01 REST API reference
Every number in your dashboard, over HTTP.
Programmatic access to keyword rankings, SERP data, keyword research, competitor analysis, and Core Web Vitals.
v1.0.035 endpointsOpenAPI 3.1Bearer auth
Authentication
Every request carries your API key as a bearer token. No OAuth dance, no session to keep alive, no signing. The base URL is https://serpdino.com.
GET https://serpdino.com/api/projects
Authorization: Bearer sd_your_key_hereCreate and revoke keys in Dashboard → Settings → API Keys. Up to five active keys per account. API access is a paid-plan feature: a key issued while subscribed stops working the moment the account drops back to Free.
Use with AI agents (MCP)
Point any MCP-compatible client at this URL with your API key as the bearer token. All 27 tools become available immediately — nothing to install.
https://serpdino.com/api/mcpWorks with Claude (web & desktop), ChatGPT (Developer mode), Cursor, Claude Code, and any client speaking the Model Context Protocol. The MCP setup guide has per-client instructions; the serpdino-mcp repo covers the local install.
Quick start
Three calls from empty account to a ranking history you can graph.
01 — List your projects
curl -H "Authorization: Bearer sd_your_key_here" \
https://serpdino.com/api/projects02 — Add keywords to a project
curl -X POST \
-H "Authorization: Bearer sd_your_key_here" \
-H "Content-Type: application/json" \
-d '{"projectId":"PROJECT_ID","keywords":["seo tools","rank tracker"],"geoCode":"US","langCode":"en"}' \
https://serpdino.com/api/projects/keywords03 — Read the rankings back
curl -H "Authorization: Bearer sd_your_key_here" \
"https://serpdino.com/api/projects/keyword-updates?projectId=PROJECT_ID"Errors
- All error responses are JSON. The error message lives in the
messagefield (NOTerror). - Some responses include
success: falsealongsidemessage; account/auth errors may omitsuccessentirely. Clients should treat any non-2xx status ORsuccess === falseas an error. /api/tools/keyword-researchreturns HTTP 200 with{ success: false, message }for all expected failures (bad seed, unsupported geo/lang, upstream timeout). Always checksuccessin addition to HTTP status./api/projects/keyword-ideasreturns HTTP 200 with{ success: true, ideas: [] }even when the upstream scraper is unreachable — an emptyideasarray means "no data available right now," not necessarily success.- Every endpoint on this API requires an active paid subscription. The Free plan has full access to the dashboard but not to the API: a key issued while the account was subscribed stops working the moment it drops to Free, and returns HTTP 403
{ message: "The Free plan does not include API access…", planRequired: true }. - Many read endpoints bypass auth entirely when the target project has
shared: true(public dashboard links). Affected:/api/projects/:id,keyword-updates,position-history,keyword-volumes,competitor-positions,pages,pages-pagespeed,notes(GET),export,export-agent. - These domain-level read endpoints require an API key (or dashboard session):
/api/projects/competitors-filtered,/api/projects/pagespeed,/api/projects/crux-history,/api/projects/similarweb. They read from our DB cache only — no live upstream calls. Each is rate-limited to 60 req / min (600 / hour) per account, plus a daily cap of 400 new-to-us domains. Cached domains never count against that cap.
{ "success": false, "message": "Description of what went wrong" }| Status | Meaning | When |
|---|---|---|
400 | Bad Request | Missing required parameter or invalid value |
401 | Unauthorized | Missing or invalid API key. Body: { message } or { success: false } |
403 | Forbidden | No API access on the current plan (Free), or the account is blocked. Body: { message, planRequired: true } |
404 | Not Found | Resource (project, folder, note) does not exist or belongs to another user |
405 | Method Not Allowed | HTTP method not supported for this endpoint |
429 | Too Many Requests | Rate limit exceeded. /api/tools/serp-check allows 10 req / 60s per API key, /api/tools/traffic-check 20 req / 60s. /api/tools/keyword-research and /api/projects/keyword-ideas allow 15 req / min (150 / hour) per account. competitors-filtered / pagespeed / crux-history / similarweb allow 60 req / min (600 / hour) per account, plus a daily quota of 400 new-to-us domains. Body includes retryAfter (seconds); a Retry-After header is also set. |
500 | Server Error | Internal server error |
Projects
Create and manage SEO tracking projects.
/api/projectsList all projects
{
"success": true,
"projects": [
{
"_id": "65f1c2a8e1234567890abcde",
"name": "Acme Corp",
"domain": "acme.com",
"aliases": [],
"competitors": [
"competitor.com"
],
"folder": null,
"updateFrequency": "daily",
"serpTop": 50,
"createdDate": "2025-01-15T08:32:11.000Z"
}
]
}curl -H "Authorization: Bearer sd_your_key_here" \
https://serpdino.com/api/projects/api/projects/:idGet project details
Includes keywords[], domain, competitors[], and full settings.
Parameters
idstring · path · required — Project ID (Mongo ObjectId)
{
"success": true,
"project": {
"_id": "65f1c2a8e1234567890abcde",
"name": "Acme Corp",
"domain": "acme.com",
"competitors": [
"competitor.com"
],
"keywords": [
{
"_id": "65f1c2a8e1234567890fffff",
"keyword": "seo tools",
"geoCode": "US",
"langCode": "en"
}
]
}
}curl -H "Authorization: Bearer sd_your_key_here" \
https://serpdino.com/api/projects/PROJECT_ID/api/projectsCreate a new project → 201
Returns HTTP 201 on success. Fires fire-and-forget triggers for keyword suggestions, SimilarWeb, PageSpeed, and competitor analysis.
Parameters
namestring · body · required — Project namedomainstring · body · required — Target domain (e.g. example.com). Lowercased server-side.folderstring · body — Folder ID to place project iniconstring · body — Icon identifier or URLaliasesstring · body — Comma/newline-separated alternative domains (treated as same property)competitorsstring[] · body — Competitor domains (PUT only — POST ignores this field)sharedboolean · body — Whether project is shared via public link (default: false)updateFrequencyenum · body — daily | every3days | weekly | biweekly | custom (default: daily)customUpdateDaysnumber[] · body — Required when updateFrequency=custom. Array of weekday numbers 0–6 (0=Sunday).updateTimenumber · body — Hour of day 0–23 to run updates (default: 12)updateTimezonestring · body — IANA timezone name (default: UTC)serpTopnumber · body — 50 | 100 — depth of SERP scrape (default: 50)
{
"success": true,
"project": {
"_id": "65f1c2a8e1234567890abcde",
"name": "My Project",
"domain": "example.com",
"updateFrequency": "daily",
"serpTop": 50
},
"message": "Project created successfully"
}curl -X POST \
-H "Authorization: Bearer sd_your_key_here" \
-H "Content-Type: application/json" \
-d '{"name":"My Project","domain":"example.com"}' \
https://serpdino.com/api/projects/api/projectsUpdate project settings
_id, name, and domain are ALL required (the handler rejects with 400 if any is missing). To change a single field, send the existing values for the others.
Parameters
_idstring · body · required — Project IDnamestring · body · required — Project name (required even if unchanged)domainstring · body · required — Target domain (required even if unchanged)competitorsstring[] · body — Array of competitor domainsfolderstring · body — Folder ID, or empty string to remove from foldericonstring · body — Icon identifier or URLaliasesstring · body — Comma/newline-separated alias domainssharedboolean · body — Public sharing toggleupdateFrequencyenum · body — daily | every3days | weekly | biweekly | customcustomUpdateDaysnumber[] · body — Required when updateFrequency=custom. Weekday numbers 0–6.updateTimenumber · body — Hour 0–23updateTimezonestring · body — IANA timezoneserpTopnumber · body — 50 | 100
{
"success": true,
"project": {
"_id": "65f1c2a8e1234567890abcde",
"name": "Acme",
"domain": "acme.com",
"competitors": [
"competitor.com"
]
},
"message": "Project updated successfully"
}curl -X PUT \
-H "Authorization: Bearer sd_your_key_here" \
-H "Content-Type: application/json" \
-d '{"_id":"PROJECT_ID","name":"Acme","domain":"acme.com","competitors":["competitor.com"]}' \
https://serpdino.com/api/projects/api/projectsDelete a project and all its data
Parameters
_idstring · body · required — Project ID
{
"success": true,
"message": "Project deleted successfully",
"projects": [],
"folders": []
}curl -X DELETE \
-H "Authorization: Bearer sd_your_key_here" \
-H "Content-Type: application/json" \
-d '{"_id":"PROJECT_ID"}' \
https://serpdino.com/api/projectsFolders
Organise projects into folders.
/api/projects/foldersList folders
{
"success": true,
"folders": [
{
"_id": "FOLDER_ID",
"name": "Clients",
"position": 0
}
]
}curl -H "Authorization: Bearer sd_your_key_here" \
https://serpdino.com/api/projects/folders/api/projects/foldersCreate folder → 201
Returns HTTP 409 { message: "Folder with this name already exists" } if a folder with that name (case-insensitive) already exists for this user. Requires active subscription.
Parameters
namestring · body · required — Folder name
{
"success": true,
"folder": {
"_id": "FOLDER_ID",
"name": "My Folder",
"position": 0
},
"message": "Folder created successfully"
}curl -X POST \
-H "Authorization: Bearer sd_your_key_here" \
-H "Content-Type: application/json" \
-d '{"name":"My Folder"}' \
https://serpdino.com/api/projects/folders/api/projects/foldersRename folder
Returns HTTP 409 on name conflict. Requires active subscription.
Parameters
idstring · query · required — Folder IDnamestring · body · required — New folder name
{
"success": true,
"folder": {
"_id": "FOLDER_ID",
"name": "New Name"
},
"message": "Folder updated successfully"
}curl -X PUT \
-H "Authorization: Bearer sd_your_key_here" \
-H "Content-Type: application/json" \
-d '{"name":"New Name"}' \
"https://serpdino.com/api/projects/folders?id=FOLDER_ID"/api/projects/foldersDelete folder
Detaches all projects in the folder (they remain, just unassigned), then deletes the folder. Returns the refreshed folders[] and projects[] lists. Requires active subscription.
Parameters
idstring · query · required — Folder ID
{
"success": true,
"message": "Folder \"My Folder\" deleted successfully",
"folders": [],
"projects": []
}curl -X DELETE -H "Authorization: Bearer sd_your_key_here" \
"https://serpdino.com/api/projects/folders?id=FOLDER_ID"Keywords
Track keywords inside a project.
/api/projects/keywordsAdd keywords to track in a project
Max 3500 keywords per project. Keywords appear in rankings after the next scheduled SERP check. Requires active subscription. Duplicates within the request and against existing project keywords are silently deduped. When the limit is exceeded, returns HTTP 400 with extended fields { error: "KEYWORDS_LIMIT_EXCEEDED", currentCount, maxAllowed, availableSlots }.
Parameters
projectIdstring · body · required — Project IDkeywordsstring[] · body · required — Array of keyword strings (lowercased server-side)geoCodestring · body · required — ISO-2 country code (e.g. US). Required — no default.langCodestring · body · required — ISO-2 language code (e.g. en). Required — no default.
{
"success": true,
"message": "Added 2 new keywords",
"addedCount": 2,
"project": {
"_id": "PROJECT_ID",
"keywords": [
"KW_ID_1",
"KW_ID_2"
]
}
}curl -X POST \
-H "Authorization: Bearer sd_your_key_here" \
-H "Content-Type: application/json" \
-d '{"projectId":"PROJECT_ID","keywords":["seo tools","rank tracker"],"geoCode":"US","langCode":"en"}' \
https://serpdino.com/api/projects/keywords/api/projects/keywordsRemove tracked keywords
Parameters
projectIdstring · body · required — Project IDkeywordIdsstring[] · body · required — Array of keyword IDs to remove
{
"success": true,
"removedCount": 2,
"project": {
"_id": "PROJECT_ID"
}
}curl -X DELETE \
-H "Authorization: Bearer sd_your_key_here" \
-H "Content-Type: application/json" \
-d '{"projectId":"PROJECT_ID","keywordIds":["KW_ID_1","KW_ID_2"]}' \
https://serpdino.com/api/projects/keywords/api/scrape/new-keywordsTrigger a fresh SERP check for keywords
Async — returns immediately (HTTP 200, status: "processing") while scraping happens in the background. Costs 1 balance credit per keyword (×2 for serpTop=100); rejects with HTTP 400 "Insufficient balance" if keywordIds.length > virtualBalance. Pass all project keyword IDs to refresh everything. Requires active subscription. All keywordIds must belong to the given project.
Parameters
projectIdstring · body · required — Project IDkeywordIdsstring[] · body · required — Keyword IDs to refresh (must belong to projectId)
{
"success": true,
"message": "Accepted 10 keywords for processing. Position updates started in background mode.",
"status": "processing",
"summary": {
"total": 10,
"accepted": 10,
"errors": 0
}
}curl -X POST \
-H "Authorization: Bearer sd_your_key_here" \
-H "Content-Type: application/json" \
-d '{"projectId":"PROJECT_ID","keywordIds":["KW_ID_1"]}' \
https://serpdino.com/api/scrape/new-keywordsRanking Data
Read keyword positions, SERP snapshots, and volume data.
/api/projects/keyword-updatesGet keyword position history
Public when project is shared. Response.data is keyed by keyword ID; each entry contains the full Keywords document plus an updates array of dated check results. When startDate/endDate are omitted, defaults to last 30 days.
Parameters
projectIdstring · query · required — Project IDstartDatestring · query — YYYY-MM-DD (default: 30 days ago)endDatestring · query — YYYY-MM-DD (default: today)aggregationenum · query — daily | weekly | monthly (default: daily)searchstring · query — Filter keywords by substring (case-insensitive)geoCodestring · query — Filter by country code (uppercased)langCodestring · query — Filter by language code (lowercased)sortByenum · query — keyword | position | volumesortOrderenum · query — asc | desc (default: desc)
{
"success": true,
"data": {
"65f1c2a8e1234567890fffff": {
"keyword": {
"_id": "65f1c2a8e1234567890fffff",
"value": "seo tools",
"geoCode": "US",
"langCode": "en",
"avgMonthlySearches": 8100
},
"updates": [
{
"_id": "UPD_ID",
"date": "2025-01-16T12:00:00.000Z",
"status": "success",
"position": {
"position": 9,
"url": "https://example.com/seo-tools",
"title": "...",
"domain": "example.com"
},
"aiSerpId": null
},
{
"_id": "UPD_ID2",
"date": "2025-01-15T12:00:00.000Z",
"status": "success",
"position": {
"position": 12,
"url": "https://example.com/seo-tools",
"title": "...",
"domain": "example.com"
},
"aiSerpId": null
}
]
}
}
}curl -H "Authorization: Bearer sd_your_key_here" \
"https://serpdino.com/api/projects/keyword-updates?projectId=PROJECT_ID&startDate=2025-01-01&endDate=2025-01-31"/api/projects/position-historyGet full SERP snapshot for a keyword check
Returns top 30 results plus the project domain itself if it ranks beyond 30. Each result includes movement vs the previous successful check (up/down/same/new). Public when project is shared.
Parameters
keywordUpdateIdstring · query · required — Keyword update ID (from keyword-updates response)
{
"success": true,
"data": {
"keywordId": "65f1c2a8e1234567890fffff",
"keywordValue": "seo tools",
"date": "2025-01-16T12:00:00.000Z",
"resultList": [
{
"position": 1,
"title": "...",
"domain": "example.com",
"url": "https://example.com",
"previousPosition": 2,
"movement": {
"type": "up",
"value": 1
}
}
],
"droppedDomains": [
{
"domain": "old-competitor.com",
"previousPosition": 15,
"movement": {
"type": "gone",
"value": null
}
}
],
"hasPreviousData": true,
"aiOverviewResults": [
{
"position": 1,
"title": "...",
"domain": "wikipedia.org",
"url": "https://...",
"isOurDomain": false
}
],
"aiContainsOurDomain": false
}
}curl -H "Authorization: Bearer sd_your_key_here" \
"https://serpdino.com/api/projects/position-history?keywordUpdateId=UPDATE_ID"/api/projects/keyword-volumesGet search volume, CPC, and competition data
Two modes with different response shapes. Bulk (no keywordId) uses compact field names to minimise payload size. Single mode (with keywordId) returns the full per-keyword detail. CPC values are pre-converted to the project currency. Public when project is shared.
Parameters
projectIdstring · query · required — Project IDkeywordIdstring · query — Single keyword ID — when present, returns full volume history for one keyword instead of the bulk map
{
"success": true,
"data": {
"65f1c2a8e1234567890fffff": {
"v": "seo tools",
"g": "US",
"l": "en",
"s": "google",
"lv": 8100,
"tr": [
6600,
7200,
7800,
8000,
8100,
8100,
7900,
8000,
8100,
8200,
8000,
8100
],
"am": 8100,
"ci": 72,
"cl": 3.1,
"ch": 5.4
}
}
}curl -H "Authorization: Bearer sd_your_key_here" \
"https://serpdino.com/api/projects/keyword-volumes?projectId=PROJECT_ID"Keyword Research
Discover new keywords and run live SERP / traffic checks.
/api/tools/serp-checkLive SERP check — returns top results and search volume for a keyword
Rate limited: 10 req / 60s per API user — all keys on one account share the bucket. 429 response body includes retryAfter (seconds), limit, window. Returns HTTP 502 if both upstream SERP and volume calls fail. Search volume is served from Serpdino's own keyword database, matched on keyword + country: if we have no data for that pair, volume.metrics is null and volume.volume is [] while the SERP part is still returned in full.
Parameters
keywordstring · body · required — Search querygeostring · body · required — ISO-2 country code (e.g. US)langstring · body · required — ISO-2 language code (e.g. en)
{
"success": true,
"data": {
"serp": [
{
"position": 1,
"url": "https://...",
"title": "...",
"snippet": "..."
}
],
"volume": {
"value": 1300,
"cpc": 2.5,
"competition": "MEDIUM"
}
},
"remaining": 9
}curl -X POST \
-H "Authorization: Bearer sd_your_key_here" \
-H "Content-Type: application/json" \
-d '{"keyword":"rank tracker","geo":"US","lang":"en"}' \
https://serpdino.com/api/tools/serp-check/api/tools/traffic-checkDomain traffic check — returns SimilarWeb stats and PageSpeed data
Rate limited: 20 req / 60s per API user — all keys on one account share the bucket. A daily quota of 400 new-to-us domains also applies — cached domains never count against it. Triggers a live scraper fetch on DB cache miss for either SimilarWeb or PageSpeed (slow path), or returns cached data immediately. data may be null if SimilarWeb has no info on the domain. Domain is validated and normalised server-side (strips protocol, www., path).
Parameters
domainstring · body · required — Target domain (e.g. example.com or https://www.example.com/path — normalised server-side)
{
"success": true,
"domain": "example.com",
"data": {
"stats": {
"domain": "example.com",
"totalVisits": 1234567,
"engagementMetrics": {
"bounceRate": 0.42,
"avgVisitDuration": 134
},
"fetchedAt": "2025-01-16T08:00:00.000Z"
},
"monthlyVisits": [
{
"domain": "example.com",
"month": "2025-01-01",
"visits": 1234567
}
]
},
"pagespeed": {
"domain": "example.com",
"performanceScore": 86,
"cruxScore": 78,
"largestContentfulPaint": 1.8,
"cumulativeLayoutShift": 0.05,
"fetchedAt": "2025-01-16T08:00:00.000Z"
},
"remaining": 19
}curl -X POST \
-H "Authorization: Bearer sd_your_key_here" \
-H "Content-Type: application/json" \
-d '{"domain":"example.com"}' \
https://serpdino.com/api/tools/traffic-check/api/projects/keyword-suggestionsAI-generated keyword suggestions for a project
Polling endpoint — ready: false (with no other fields) means the LLM pipeline is still running. Retry every few seconds for ~2 minutes max. Generation is auto-triggered at project creation, so this is just a reader over the cached suggestions doc. Requires active subscription. Each locale ships up to 30 phrases, sorted by lastVolume desc.
Parameters
projectIdstring · query · required — Project ID
{
"success": true,
"ready": true,
"domain": "example.com",
"primary": {
"lang": "en",
"geo": "US"
},
"locales": [
{
"key": "en-US",
"lang": "en",
"region": "US",
"geo": "US",
"lastUpdatedAt": "2025-01-16T08:00:00.000Z",
"keywords": [
{
"phrase": "best seo tools",
"lastVolume": 8100,
"volume": [
{
"year": 2024,
"months": [
{
"month": 12,
"value": 8100
}
]
}
]
}
]
}
],
"pages": [],
"meta": {
"seedUsed": "domain"
},
"runCount": 1,
"lastRunAt": "2025-01-16T08:00:00.000Z",
"lastError": null
}curl -H "Authorization: Bearer sd_your_key_here" \
"https://serpdino.com/api/projects/keyword-suggestions?projectId=PROJECT_ID"/api/projects/keyword-ideasKeyword ideas based on a project's domain
Soft-fail endpoint: always returns HTTP 200 with success: true when authorised. On upstream errors returns { success: true, ideas: [], error: "upstream_error" | "service_unavailable" } — an empty ideas array does NOT mean failure, it means "no data available right now". 30s timeout on the upstream call. Requires active subscription. Rate limited for API-key callers: 15 req / min, 150 req / hour per account — HTTP 429 with retryAfter when exceeded.
Parameters
projectIdstring · body · required — Project IDgeostring · body — Country code (default: US, uppercased)langstring · body — Language code (default: en, lowercased)
{
"success": true,
"ideas": [
{
"keyword": "seo tools comparison",
"avgMonthlySearches": 1000,
"competition": "MEDIUM"
}
]
}curl -X POST \
-H "Authorization: Bearer sd_your_key_here" \
-H "Content-Type: application/json" \
-d '{"projectId":"PROJECT_ID"}' \
https://serpdino.com/api/projects/keyword-ideasCompetitors
Compare competitor rankings and traffic.
/api/projects/competitor-positionsGet a competitor's ranking positions across all tracked keywords
Searches the project's tracked keywords' SERP history for results matching the competitor domain. position: 0 means the competitor didn't appear in the top 30 results for that check. Public when project is shared. Deduped to one entry per calendar day per keyword.
Parameters
projectIdstring · query · required — Project IDcompetitorDomainstring · query · required — Competitor domain (e.g. competitor.com — normalised server-side)startDatestring · query — YYYY-MM-DDendDatestring · query — YYYY-MM-DD
{
"success": true,
"keywordHistory": {
"65f1c2a8e1234567890fffff": {
"keyword": {
"_id": "65f1c2a8e1234567890fffff",
"value": "seo tools",
"geoCode": "US",
"langCode": "en"
},
"updates": [
{
"_id": "UPD_ID",
"date": "2025-01-15T12:00:00.000Z",
"status": "success",
"position": {
"position": 4,
"domain": "competitor.com",
"title": "-",
"url": "-"
},
"aiSerpId": null
}
]
}
}
}curl -H "Authorization: Bearer sd_your_key_here" \
"https://serpdino.com/api/projects/competitor-positions?projectId=PROJECT_ID&competitorDomain=competitor.com"/api/projects/competitors-filteredCompare traffic and performance across a domain and its competitors
Requires an API key or dashboard session (401 otherwise). Rate limited: 60 req / min (600 / hour) per identity, plus a daily new-domain quota. Reads from the SimilarWeb / PageSpeed cache only (no live fetching). Auto-filters competitors to a balanced sample of 6 (3 above, 3 below main domain traffic) when more than 6 are supplied. data is keyed by domain.
Parameters
domainstring · query · required — Main domaincompetitorsstring · query · required — Comma-separated competitor domains, or repeated query param. Max 50 accepted (HTTP 400 above); auto-filtered to 6 in the response.
{
"success": true,
"data": {
"example.com": {
"stats": {
"domain": "example.com",
"totalVisits": 1234567,
"fetchedAt": "2025-01-16T08:00:00.000Z"
},
"monthlyVisits": [
{
"month": "2025-01-01",
"visits": 1234567
}
],
"pageSpeed": {
"cruxScore": 78
}
}
},
"filtered": [
"competitor.com"
],
"mainDomainVisits": 1234567,
"totalCompetitors": 1
}curl -H "Authorization: Bearer sd_your_key_here" \
"https://serpdino.com/api/projects/competitors-filtered?domain=example.com&competitors=a.com,b.com"Performance
Page-level rankings and Core Web Vitals.
/api/projects/pagesPage-level ranking data: which URLs rank, average position, trend sparklines
Aggregated by the projectPagesPoller. Each page includes 14-point trend data for the last 90 days, bucketed weekly (or daily if there is not enough data for a week-bucket). Public when project is shared. Sorted worst-first by avgPosition.
Parameters
projectIdstring · query · required — Project ID
{
"success": true,
"data": [
{
"url": "https://example.com/blog/post",
"path": "/blog/post",
"keywordCount": 12,
"avgPosition": 8.4,
"bestPosition": 3,
"keywords": [
{
"keywordId": "KW_ID",
"value": "seo tools",
"position": 5
}
],
"avgPositionTrend": [
{
"date": "2025-01-06T00:00:00.000Z",
"avgPosition": 9.2,
"keywordCount": 12
}
],
"avgPositionTrendGranularity": "week",
"weeklyAvgPositions": [
{
"weekStart": "2025-01-06T00:00:00.000Z",
"avgPosition": 9.2,
"keywordCount": 12
}
]
}
],
"lastPagesUpdate": "2025-01-16T08:00:00.000Z"
}curl -H "Authorization: Bearer sd_your_key_here" \
"https://serpdino.com/api/projects/pages?projectId=PROJECT_ID"/api/projects/pagespeedPageSpeed Insights (Core Web Vitals) for a domain
Requires an API key or dashboard session (401 otherwise). Rate limited: 60 req / min (600 / hour) per identity, plus a daily new-domain quota. Reads from DB cache only (never triggers a live PageSpeed run). Returns data: null if domain not in cache. Domain is normalised (strips www., lowercased).
Parameters
domainstring · query · required — Target domain
{
"success": true,
"data": {
"domain": "example.com",
"performanceScore": 86,
"accessibilityScore": 92,
"bestPracticesScore": 90,
"seoScore": 95,
"cruxScore": 78,
"largestContentfulPaint": 1.8,
"firstContentfulPaint": 1,
"cumulativeLayoutShift": 0.05,
"totalBlockingTime": 120,
"cruxFieldData": {
"LCP": {
"p75": 1900
},
"CLS": {
"p75": 0.05
},
"INP": {
"p75": 180
}
},
"fetchedAt": "2025-01-16T08:00:00.000Z"
}
}curl -H "Authorization: Bearer sd_your_key_here" \
"https://serpdino.com/api/projects/pagespeed?domain=example.com"/api/projects/pages-pagespeedPer-page Lighthouse lab metrics for all tracked pages in a project
Public when project is shared. pages is keyed by URL. baseline is the home-page record when available, falling back to the domain-level snapshot (with source: "domain") so the UI can still compute deltas. Returns baseline: null if neither exists.
Parameters
projectIdstring · query · required — Project ID
{
"success": true,
"baseline": {
"url": "https://example.com/",
"path": "/",
"domain": "example.com",
"performanceScore": 86,
"largestContentfulPaint": 1.8,
"firstContentfulPaint": 1,
"cumulativeLayoutShift": 0.04,
"totalBlockingTime": 120,
"source": "page"
},
"pages": {
"https://example.com/blog/post": {
"url": "https://example.com/blog/post",
"path": "/blog/post",
"performanceScore": 80,
"largestContentfulPaint": 2.1,
"source": "page"
}
}
}curl -H "Authorization: Bearer sd_your_key_here" \
"https://serpdino.com/api/projects/pages-pagespeed?projectId=PROJECT_ID"/api/projects/crux-historyChrome UX Report (CrUX) real-user performance history for a domain
Requires an API key or dashboard session (401 otherwise). Rate limited: 60 req / min (600 / hour) per identity, plus a daily new-domain quota. Reads from DB cache only. data is the origin-level series (CrUX records with no specific URL); pages is per-page records keyed by URL with parsed path.
Parameters
domainstring · query · required — Target domain
{
"success": true,
"data": [
{
"domain": "example.com",
"date": "2025-01-01",
"lcp": {
"p75": 1900
},
"cls": {
"p75": 0.05
}
}
],
"pages": [
{
"url": "https://example.com/blog/",
"path": "/blog/",
"data": [
{
"date": "2025-01-01",
"lcp": {
"p75": 2100
}
}
]
}
]
}curl -H "Authorization: Bearer sd_your_key_here" \
"https://serpdino.com/api/projects/crux-history?domain=example.com"/api/projects/similarwebSimilarWeb traffic stats and monthly visit history for a domain
Requires an API key or dashboard session (401 otherwise). Rate limited: 60 req / min (600 / hour) per identity, plus a daily new-domain quota. Reads from DB cache only. exists: false and data: null when the domain has never been scraped. monthlyVisits capped at 12 months, oldest first.
Parameters
domainstring · query · required — Target domain
{
"success": true,
"exists": true,
"data": {
"stats": {
"domain": "example.com",
"totalVisits": 1234567,
"fetchedAt": "2025-01-16T08:00:00.000Z"
},
"monthlyVisits": [
{
"domain": "example.com",
"month": "2024-02-01",
"visits": 1100000
},
{
"domain": "example.com",
"month": "2025-01-01",
"visits": 1234567
}
]
}
}curl -H "Authorization: Bearer sd_your_key_here" \
"https://serpdino.com/api/projects/similarweb?domain=example.com"Notes
Timeline annotations for a project.
/api/projects/notesList timeline notes for a project
Parameters
projectIdstring · query · required — Project ID
{
"success": true,
"data": [
{
"_id": "NOTE_ID",
"date": "2025-01-15",
"text": "Google algorithm update",
"color": "warning"
}
]
}curl -H "Authorization: Bearer sd_your_key_here" \
"https://serpdino.com/api/projects/notes?projectId=PROJECT_ID"/api/projects/notesAdd or update a note on a project timeline → 201
Upsert by (projectId, date): if a note already exists for this date, it is updated in place and the response is HTTP 200 with message: "Note updated". Otherwise a new note is created and the response is HTTP 201 with message: "Note saved". Requires active subscription.
Parameters
projectIdstring · body · required — Project IDdatestring · body · required — YYYY-MM-DD (interpreted as 00:00:00 UTC)textstring · body · required — Note contentcolorenum · body — info | warning | success | danger (default: info, only set on new notes)
{
"success": true,
"message": "Note saved",
"data": {
"_id": "NOTE_ID",
"projectId": "PROJECT_ID",
"date": "2025-01-15T00:00:00.000Z",
"text": "Google algorithm update",
"color": "warning"
}
}curl -X POST \
-H "Authorization: Bearer sd_your_key_here" \
-H "Content-Type: application/json" \
-d '{"projectId":"PROJECT_ID","date":"2025-01-15","text":"Google algorithm update","color":"warning"}' \
https://serpdino.com/api/projects/notes/api/projects/notesDelete a note
Returns the remaining notes for the project in data after deletion. Requires active subscription.
Parameters
noteIdstring · body · required — Note ID
{
"success": true,
"message": "Note deleted",
"data": [
{
"_id": "OTHER_NOTE_ID",
"date": "2025-01-10T00:00:00.000Z",
"text": "...",
"color": "info"
}
]
}curl -X DELETE \
-H "Authorization: Bearer sd_your_key_here" \
-H "Content-Type: application/json" \
-d '{"noteId":"NOTE_ID"}' \
https://serpdino.com/api/projects/notesAccount
Check usage and limits.
/api/user/capacityCheck account usage and limits
The one place to read every limit that applies to your account, so a client can pace itself instead of discovering limits by being rejected.
total / booked / available are monthly SERP-check tokens — the budget consumed by tracking keywords, calculated as keywords × refreshes per month (× 2 when serpTop is 100). There is no limit on the number of projects (websites) — limits.projects is null, meaning unlimited. Running out of tokens is what stops you adding more tracking, not a website count.
limits.rateLimits reports the per-API-key ceilings enforced on the endpoints of the same name; domainReads covers competitors-filtered / pagespeed / crux-history / similarweb.
limits.rateLimits.newDomainsPerDay is the odd one out: not a per-minute rate but a daily allowance of domains that are new to us, and a single bucket shared by every endpoint in its appliesTo list rather than one allowance each. Domains already in our cache never consume it, so re-checking a domain you looked up last week is free. resetsInSeconds is the time until the allowance clears if you look up no further new domains — each newly counted domain re-arms the 24h window — and is null when nothing has been counted yet.
limits is always read live and never served from cache.
{
"total": 12000,
"booked": 320,
"available": 11680,
"projects": 4,
"keywords": 320,
"plan": "paid",
"limits": {
"keywordsPerProject": 3500,
"projects": null,
"apiKeys": {
"limit": 5,
"used": 2
},
"competitorsPerRequest": 50,
"rateLimits": {
"serpCheck": {
"perMinute": 10,
"windowSeconds": 60
},
"trafficCheck": {
"perMinute": 20,
"windowSeconds": 60
},
"keywordResearch": {
"perMinute": 15,
"perHour": 150
},
"domainReads": {
"perMinute": 60,
"perHour": 600
},
"newDomainsPerDay": {
"limit": 400,
"used": 37,
"remaining": 363,
"resetsInSeconds": 61840,
"appliesTo": [
"/api/tools/traffic-check",
"/api/projects/similarweb",
"/api/projects/pagespeed",
"/api/projects/crux-history",
"/api/projects/competitors-filtered"
]
}
}
}
}curl -H "Authorization: Bearer sd_your_key_here" \
https://serpdino.com/api/user/capacityExport
Export project data as Markdown or CSV.
/api/projects/export-agentGenerate a comprehensive Markdown report for a project
Returns plain Markdown text (Content-Type: text/markdown), NOT JSON. Includes rankings, traffic, competitors, and performance data in a single document — designed for feeding to LLMs.
Parameters
projectIdstring · query · required — Project ID
# Acme Corp — SEO Report
Domain: acme.com
## Rankings
...curl -H "Authorization: Bearer sd_your_key_here" \
"https://serpdino.com/api/projects/export-agent?projectId=PROJECT_ID"/api/projects/exportCSV export of ranking data
Returns CSV with UTF-8 BOM and semicolon (;) separator — opens correctly in Excel without conversion. Content-Disposition header forces download with a generated filename. full format: one row per keyword with a date column per checked day. summary format: one row per keyword with aggregated stats (current/best/worst/avg position, change). Public when project is shared.
Parameters
projectIdstring · query · required — Project IDformatenum · query — full | summary (default: full)startDatestring · query — YYYY-MM-DDendDatestring · query — YYYY-MM-DD
Keyword;SERP;Volume;URL;Change;2025-01-16;2025-01-15
seo tools;en-US;8100;https://example.com/seo-tools;-3;9;12
curl -H "Authorization: Bearer sd_your_key_here" \
"https://serpdino.com/api/projects/export?projectId=PROJECT_ID&format=full" -o report.csvAPI Keys
Create and revoke API keys programmatically.
/api/user/api-keysList your API keys
Returns metadata only — secrets are never returned after creation. Each entry includes _id, name, keyPrefix (masked, first 4 chars only), lastUsedAt, createdAt.
{
"success": true,
"apiKeys": [
{
"_id": "KEY_ID",
"name": "My Integration",
"keyPrefix": "sd_a••••",
"lastUsedAt": "2025-01-16T10:00:00.000Z",
"createdAt": "2025-01-10T08:00:00.000Z"
}
]
}curl -H "Authorization: Bearer sd_your_key_here" \
https://serpdino.com/api/user/api-keys/api/user/api-keysCreate a new API key → 201
key is the full secret — shown only once, store it immediately. apiKey is metadata. Max 5 active keys per user. Names limited to 100 chars.
Parameters
namestring · body · required — Key name (max 100 chars)
{
"success": true,
"key": "sd_abcd1234567890abcdef1234567890abcdef1234",
"apiKey": {
"_id": "KEY_ID",
"name": "My Integration",
"keyPrefix": "sd_a••••",
"createdAt": "2025-01-16T10:00:00.000Z"
}
}curl -X POST \
-H "Authorization: Bearer sd_your_key_here" \
-H "Content-Type: application/json" \
-d '{"name":"My Integration"}' \
https://serpdino.com/api/user/api-keys/api/user/api-keysRevoke an API key
Soft-revokes by setting revokedAt. Accepts id from either body or query string. Returns 404 if the key does not exist or already revoked.
Parameters
idstring · body — API key ID (one of body or query is required)idstring · query — API key ID (one of body or query is required)
{
"success": true
}curl -X DELETE \
-H "Authorization: Bearer sd_your_key_here" \
-H "Content-Type: application/json" \
-d '{"id":"KEY_ID"}' \
https://serpdino.com/api/user/api-keys