SEO MCP tools: all 49, with their inputs
TheSerpAgent is the rank tracker your AI runs. This is every tool on its SEO MCP server, for agents and for the people connecting them: what each one does, when to call it, what it costs and what comes back.
- Server name
- TheSerpAgent. Config files use the key theserpagent. It is not listed in a directory yet, so connect it by this address
- Address
- https://api.theserpagent.ai/api/mcp
- Transport
- Streamable HTTP, JSON replies, stateless
- Protocol versions
- 2025-06-18, 2025-03-26, 2024-11-05
- Key
- Authorization: Bearer KEY, created in the app under AI assistants by the account owner
- Rate limit
- 120 tool calls a minute per account
- Reach
- One account. Tools only, with no resources or prompts
- Depth
- The top 30. A position of null means not in the top 30
Use it when
The person wants positions tracked over time, asks what moved, compares two dates, watches competitors or Google AI Overview, needs a client report, or wants an alert when a page drops.
Do not use it when
The person wants keyword research, backlinks, Bing, the Map Pack, positions below 30, or a one-off lookup with nothing to track.
Tools do not reach billing, login or API keys, which stay in the dashboard with CSV import and export. This is a rank tracker, and it offers no public SERP API.
Start here
list_projects
Read onlyThe tracked websites, with keyword counts and average position.
- When
- First, to get the project ids most other tools need.
- Cost
- Not metered.
It takes no inputs.
What comes back
{
"projects": [
{
"id": 1,
"domain": "alpinerun.example",
"name": "Alpine Run",
"location": "New York, New York, United States",
"language": "en",
"device": "desktop",
"keywords": 18,
"has_icon": false,
"average_position": 8.5
}
]
}
search_locations
Read onlyFinds a country or city and returns its location code, up to 10 matches.
- When
- Before create_project or update_project.
- Cost
- Not metered.
| Input | Type and meaning |
|---|---|
queryrequired |
string. Part of a country or city name, e.g. "Berlin". |
What comes back
{
"locations": [
{
"location_code": 1023191,
"name": "New York, New York, United States",
"type": "City"
}
]
}
list_languages
Read onlyThe Google languages a project can use, with their codes.
- When
- Before create_project or update_project, to set a language.
- Cost
- Not metered.
It takes no inputs.
What comes back
{
"languages": [
{
"code": "en",
"name": "English"
},
{
"code": "es",
"name": "Spanish"
}
]
}
get_usage
Read onlyThe plan, its limits and what is left: keywords, projects and refresh checks this month and this hour.
- When
- Before refreshing many keywords.
- Cost
- Not metered.
It takes no inputs.
What comes back
{
"plan": "starter",
"keywords": {
"used": 34,
"limit": 200
},
"projects": {
"used": 4,
"limit": 10
},
"refresh_checks": {
"left_this_month": 1000,
"monthly_limit": 1000,
"left_this_hour": 500,
"hourly_limit": 500
}
}
Read the record
get_overview
Read onlyA project's headline numbers: average position and the one before it, how many keywords sit in the top 3, 10 and 30, and today's biggest winners and losers. The average covers only the keywords in the top 30, which buckets.ranked counts. A loser with a null position left the top 30 from dropped_from.
- When
- For the state of a project at a glance.
- Cost
- Not metered.
| Input | Type and meaning |
|---|---|
project_idrequired |
integer. Project id from list_projects. |
What comes back
{
"project": {
"id": 1,
"domain": "alpinerun.example"
},
"average_position": 8.47,
"previous_average_position": 8.35,
"buckets": {
"top3": 3,
"top10": 11,
"top30": 17,
"ranked": 17,
"not_ranked": 1,
"pending": 0,
"total": 18
},
"winners": [
{
"id": 6,
"term": "trail running poles",
"position": 3,
"change": 1
}
],
"losers": [
{
"id": 1,
"term": "trail running shoes",
"position": 7,
"change": -3,
"dropped_from": null
}
]
}
get_rankings
Read onlyCurrent positions for a project's keywords, with previous and best position, monthly searches and CPC when known. Each keyword also says whether it is starred, whether Google shows an AI Overview and whether you are cited in it. A position of null means not in the top 30, or not checked yet when last_checked is also null.
- When
- To read the current state, or to get keyword ids. Filter by list, search words, stars, position range or only what moved at the last check.
- Cost
- Not metered.
| Input | Type and meaning |
|---|---|
project_idrequired |
integer. Project id from list_projects. |
tag |
string. Only keywords in this list (tag name). |
search |
string. Only keywords containing this text. |
starred |
boolean. Only starred keywords. |
min_position |
integer. Best position to include, e.g. 4 to skip the top 3. |
max_position |
integer. Worst position to include, e.g. 10 for page one. |
changed_only |
boolean. Only keywords whose position changed at the last check. |
sort |
term | position | best | last_checked | volume | cpc |
limit |
integer. Rows per page, default 50, max 200. |
page |
integer |
What comes back
{
"keywords": [
{
"id": 3,
"keyword": "best running socks",
"position": 1,
"previous_position": 1,
"best_position": 1,
"monthly_searches": 4400,
"cpc_usd": 0.64,
"url": "https://alpinerun.example/guides/best-running-socks",
"lists": [
"socks"
],
"starred": false,
"ai_overview": true,
"ai_cited": true,
"last_checked": "2026-11-07"
}
],
"page": 1,
"pages": 18,
"total": 18
}
get_keyword_history
Read onlyOne keyword's daily record: date, position and ranking page, up to 365 days back. A position of null means not in the top 30.
- When
- To quote a past position, or to see how a keyword got where it is.
- Cost
- Not metered.
| Input | Type and meaning |
|---|---|
project_idrequired |
integer. Project id from list_projects. |
keyword_idrequired |
integer. Keyword id from get_rankings. |
days |
7 | 30 | 90 | 365. How far back, default 30. |
What comes back
{
"keyword": "trail running shoes",
"history": [
{
"date": "2026-11-06",
"position": 4,
"url": "https://alpinerun.example/shoes/trail"
},
{
"date": "2026-11-07",
"position": 7,
"url": "https://alpinerun.example/shoes/trail"
}
]
}
get_changes
Read onlyThe biggest winners and losers of a project over 1, 7 or 30 days, up to 20 on each side. Change is places gained, so negative means lost. A keyword that left the top 30 is a loser with a null position and dropped_from saying where it was. One that entered the top 30 has no earlier number to compare, so it is not listed.
- When
- When the person asks what moved.
- Cost
- Not metered.
| Input | Type and meaning |
|---|---|
project_idrequired |
integer. Project id from list_projects. |
period |
1d | 7d | 30d. Compare with 1 day, 7 days (default) or 30 days ago. |
What comes back
{
"period": "7d",
"winners": [
{
"keyword": "running shoes for wide feet",
"position": 6,
"change": 2
}
],
"losers": [
{
"keyword": "trail running shoes",
"position": 7,
"change": -3
},
{
"keyword": "ultramarathon training plan",
"position": null,
"change": null,
"dropped_from": 28
}
]
}
explain_change
Read onlyCompares a keyword on two dates: its position, the stored top results and which sites entered or dropped out. It returns a difference, never a cause. By default it compares the latest check with the last check at a different position. Top results are kept 90 days; older dates give the position only.
- When
- When the person asks what changed around a keyword.
- Cost
- Not metered.
| Input | Type and meaning |
|---|---|
project_idrequired |
integer. Project id from list_projects. |
keyword_idrequired |
integer. Keyword id from get_rankings. |
from_date |
string. Earlier date, YYYY-MM-DD. Must be a day the keyword was checked. |
to_date |
string. Later date, YYYY-MM-DD. Defaults to the latest check. |
depth |
integer. How many top results to list per date, 1-30, default 10. |
What comes back
{
"keyword": "trail running shoes",
"summary": "\"trail running shoes\" moved down 3 places: 4 on 2026-11-06 to 7 on 2026-11-07.",
"change": -3,
"from": {
"date": "2026-11-06",
"position": 4,
"url": "https://alpinerun.example/shoes/trail",
"top": [
{
"position": 1,
"domain": "ridgeoutfitters.example",
"title": "Trail running shoes | ridgeoutfitters.example",
"url": "https://ridgeoutfitters.example/trail-running-shoes"
}
]
},
"to": {
"date": "2026-11-07",
"position": 7,
"url": "https://alpinerun.example/shoes/trail",
"top": [
{
"position": 1,
"domain": "ridgeoutfitters.example",
"title": "Trail running shoes | ridgeoutfitters.example",
"url": "https://ridgeoutfitters.example/trail-running-shoes"
}
]
},
"entered": [
"gearlab-reviews.example"
],
"dropped": [
"fleetfootmag.example"
],
"note": null
}
Projects
create_project
Changes dataStarts tracking a website in one location, language and device. It starts with one alert rule, moved_down 5, which update_alert or delete_alert can change.
- When
- When the person wants a new site tracked. Call search_locations first.
- Cost
- Counts against the plan's project limit. Needs a plan or a running trial.
| Input | Type and meaning |
|---|---|
domainrequired |
string. Bare domain such as example.com (no https://). |
location_coderequired |
integer. From search_locations. |
language_code |
string. Default en. |
device |
desktop | mobile. Default desktop. |
name |
string |
What comes back
{
"project": {
"id": 5,
"domain": "summitbikes.example",
"device": "mobile",
"location_code": 1023191
}
}
update_project
DestructiveChanges a project's name, domain, location, language or device. Changing location, language or device starts a new history for every keyword.
- When
- When the person changes a project. Confirm a new location, language or device first. The tool says so.
- Cost
- Needs a plan or a running trial.
| Input | Type and meaning |
|---|---|
project_idrequired |
integer. Project id from list_projects. |
name |
string |
domain |
string. Bare domain such as example.com. |
location_code |
integer. From search_locations. |
language_code |
string. From list_languages. |
device |
desktop | mobile |
What comes back
{
"project": {
"id": 5,
"name": "Summit Bikes",
"domain": "summitbikes.example",
"location_code": 1023191,
"language": "en",
"device": "mobile"
}
}
set_project_icon
Changes dataGives a project its own icon, from a public image address or base64: PNG, JPEG, WebP, GIF or ICO, up to 2 MB.
- When
- When the person wants a project's tile to show its own icon.
- Cost
- Needs a plan or a running trial.
| Input | Type and meaning |
|---|---|
project_idrequired |
integer. Project id from list_projects. |
image_url |
string. Public https:// address of the image. |
image_base64 |
string. The image itself as base64, instead of image_url. |
What comes back
{
"project_id": 5,
"icon": {
"format": "png",
"size_bytes": 237
}
}
remove_project_icon
Changes dataRemoves a project's icon, so its tile goes back to the first letter of the domain.
- When
- When the person wants the icon gone.
- Cost
- Not metered.
| Input | Type and meaning |
|---|---|
project_idrequired |
integer. Project id from list_projects. |
What comes back
{
"project_id": 5,
"icon": null
}
delete_project
DestructiveDeletes a project with all its keywords, history, competitors, alerts and report settings. It cannot be undone, and it needs the project's exact domain as confirm_domain.
- When
- Only after confirming with the person. The tool says so.
- Cost
- Not metered.
| Input | Type and meaning |
|---|---|
project_idrequired |
integer. Project id from list_projects. |
confirm_domainrequired |
string. The project's exact domain, as a safety check. |
What comes back
{
"deleted": {
"id": 5,
"domain": "summitbikes.example"
}
}
Keywords
add_keywords
Changes dataStarts tracking keywords in a project, up to 500 in a call, optionally in lists. Duplicates are skipped. Read the first positions with get_rankings once the first check is back.
- When
- When the person gives keywords to track.
- Cost
- Counts against the plan's keyword limit. Needs a plan or a running trial.
| Input | Type and meaning |
|---|---|
project_idrequired |
integer. Project id from list_projects. |
keywordsrequired |
array of string. Up to 500 keywords. |
lists |
array of string. List (tag) names to put them in. |
What comes back
{
"added": 4,
"skipped": 0,
"queued": 4
}
refresh_keywords
Changes dataChecks keywords now, by ids or by list, instead of waiting for the next daily check. Most fresh positions arrive within two minutes; a late one can take up to 6 hours, and a check that fails is given back. A keyword is fresh once its last_checked in get_rankings changes.
- When
- After a page changes, or when the person asks for a new position today.
- Cost
- One check per keyword within the monthly and hourly limits; a call past either is rejected whole. A search checked in the last hour reuses free. Needs a plan or a running trial.
| Input | Type and meaning |
|---|---|
project_idrequired |
integer. Project id from list_projects. |
keyword_ids |
array of integer. Up to 1000 ids from get_rankings. |
list |
string. Refresh every keyword in this list instead. |
What comes back
{
"checks_used": 0,
"reused_free": 2,
"checks_left_this_month": 1000,
"checks_left_this_hour": 500,
"note": "Most fresh positions arrive within two minutes; a late one can take up to 6 hours, and a check that fails is given back. A keyword is fresh once its last_checked in get_rankings changes."
}
rename_keyword
DestructiveChanges a keyword's wording. Google is then searched for the new wording, so its position starts again and the old history no longer shows.
- When
- Only after confirming with the person. The tool says so.
- Cost
- Needs a plan or a running trial.
| Input | Type and meaning |
|---|---|
project_idrequired |
integer. Project id from list_projects. |
keyword_idrequired |
integer. From get_rankings. |
termrequired |
string. The new wording. |
What comes back
{
"keyword": {
"id": 38,
"keyword": "gravel bikes",
"position": null
}
}
star_keywords
Changes dataMarks keywords as starred, or takes the star off, up to 500 ids.
- When
- To keep the keywords that matter easy to filter in get_rankings.
- Cost
- Not metered.
| Input | Type and meaning |
|---|---|
project_idrequired |
integer. Project id from list_projects. |
keyword_idsrequired |
array of integer. From get_rankings. |
starredrequired |
boolean. True to star, false to take the star off. |
What comes back
{
"changed": 2
}
remove_keywords
DestructiveStops tracking keywords and deletes their history. It needs the exact ids, 100 at most, and there is no way to remove all.
- When
- Only after confirming with the person. The tool says so.
- Cost
- Not metered. Frees keyword allowance.
| Input | Type and meaning |
|---|---|
project_idrequired |
integer. Project id from list_projects. |
keyword_idsrequired |
array of integer. Exact ids to remove (max 100). |
What comes back
{
"removed": 1
}
Sort keywords into lists
create_tag
Changes dataCreates an empty keyword list in a project.
- When
- Before sorting keywords into a new list.
- Cost
- Not metered.
| Input | Type and meaning |
|---|---|
project_idrequired |
integer. Project id from list_projects. |
namerequired |
string. List name, up to 64 characters. |
What comes back
{
"list": {
"id": 11,
"name": "autumn"
}
}
rename_tag
Changes dataRenames a keyword list. Its keywords stay in it.
- When
- When the person renames a list.
- Cost
- Not metered.
| Input | Type and meaning |
|---|---|
project_idrequired |
integer. Project id from list_projects. |
tag_idrequired |
integer. From list_tags. |
namerequired |
string |
What comes back
{
"list": {
"id": 11,
"name": "autumn sale"
}
}
tag_keywords
Changes dataPuts keywords in a list, up to 500 ids. The list is created if it does not exist.
- When
- To group keywords for filtering, refreshing or alerts.
- Cost
- Not metered.
| Input | Type and meaning |
|---|---|
project_idrequired |
integer. Project id from list_projects. |
listrequired |
string. List (tag) name. |
keyword_idsrequired |
array of integer. Up to 500 ids. |
What comes back
{
"keywords_affected": 2
}
untag_keywords
Changes dataTakes keywords out of a list, up to 500 ids. The keywords stay tracked.
- When
- When keywords no longer belong in a list.
- Cost
- Not metered.
| Input | Type and meaning |
|---|---|
project_idrequired |
integer. Project id from list_projects. |
listrequired |
string. List (tag) name. |
keyword_idsrequired |
array of integer. Up to 500 ids. |
What comes back
{
"keywords_affected": 1
}
delete_tag
DestructiveDeletes a keyword list. The keywords in it are not deleted.
- When
- Only after confirming with the person. The tool says so.
- Cost
- Not metered.
| Input | Type and meaning |
|---|---|
project_idrequired |
integer. Project id from list_projects. |
tag_idrequired |
integer. From list_tags. |
What comes back
{
"deleted_list": "autumn sale",
"keywords_deleted": 0
}
Competitors
list_competitors
Read onlyThe competitor websites a project watches, with their ids and how many the plan allows.
- When
- Before adding or removing a competitor.
- Cost
- Not metered.
| Input | Type and meaning |
|---|---|
project_idrequired |
integer. Project id from list_projects. |
What comes back
{
"competitors": [
{
"id": 2,
"domain": "runnersdepot.example"
},
{
"id": 3,
"domain": "summitgear.example"
},
{
"id": 1,
"domain": "trailhouse.example"
}
],
"limit_per_project": 3
}
add_competitor
Changes dataWatches a competitor's website in a project. Its positions come from the daily checks already run for your keywords.
- When
- When the person names a rival to watch.
- Cost
- Uses no refresh checks. Counts against the plan's competitors per project. Needs a plan or a running trial.
| Input | Type and meaning |
|---|---|
project_idrequired |
integer. Project id from list_projects. |
domainrequired |
string. Bare domain such as rival.com. |
What comes back
{
"competitor": {
"id": 9,
"domain": "rivalbikes.example"
}
}
get_competitor_rankings
Read onlyKeyword by keyword: your position beside each competitor's latest position, and whether Google shows an AI Overview and cites you in it. A position of null means not in the top 30.
- When
- When the person asks who ranks above them.
- Cost
- Not metered.
| Input | Type and meaning |
|---|---|
project_idrequired |
integer. Project id from list_projects. |
limit |
integer. Rows per page, default 50, max 100. |
page |
integer |
What comes back
{
"keywords": [
{
"id": 3,
"keyword": "best running socks",
"you": 1,
"competitors": {
"runnersdepot.example": 4,
"summitgear.example": 12,
"trailhouse.example": 3
},
"ai_overview": true,
"ai_cited": true
}
],
"page": 1,
"pages": 18,
"total": 18
}
get_visibility
Read onlyShare of visibility for you and each competitor, per day and now, over 7 to 90 days. It is an estimate from typical click rates by position, and it measures no visits.
- When
- When the person asks how they compare overall.
- Cost
- Not metered.
| Input | Type and meaning |
|---|---|
project_idrequired |
integer. Project id from list_projects. |
days |
integer. Days covered, 7 to 90. Default 30. |
What comes back
{
"domains": [
{
"domain": "alpinerun.example",
"is_you": true,
"competitor_id": null,
"score": 15.5,
"change": 0
},
{
"domain": "runnersdepot.example",
"is_you": false,
"competitor_id": 2,
"score": 14.8,
"change": -0.8
}
],
"series": [
{
"date": "2026-11-07",
"scores": {
"alpinerun.example": 15.5,
"runnersdepot.example": 14.8
}
}
]
}
remove_competitor
DestructiveStops watching a competitor and deletes its stored positions.
- When
- Only after confirming with the person. The tool says so.
- Cost
- Not metered. Frees a competitor place.
| Input | Type and meaning |
|---|---|
project_idrequired |
integer. Project id from list_projects. |
competitor_idrequired |
integer. From list_competitors. |
What comes back
{
"removed": {
"id": 9,
"domain": "rivalbikes.example"
}
}
Alerts
create_alert
Changes dataSets a rule. When a keyword crosses it, the account owner gets an email summary, at most one a day per project; a move checked after that day's summary goes in the next one. A rule watches the whole project or one list. moved_up sees a keyword in the top 30 at both checks. moved_down also fires, whatever the threshold, for one that leaves the top 30. Asking for a rule the project already has returns that rule, switched on, with created false.
- When
- When the person wants to hear about a drop or a gain.
- Cost
- Not metered.
| Input | Type and meaning |
|---|---|
project_idrequired |
integer. Project id from list_projects. |
typerequired |
moved_up | moved_down | entered_top3 | left_top3 | entered_top10 | left_top10 | dropped_out |
threshold |
integer. Places moved, 1-29. Only for moved_up and moved_down. |
list |
string. Only watch keywords in this list. |
What comes back
{
"alert": {
"id": 7,
"rule": "Moved down 3+ places",
"list": "Spring"
},
"created": true
}
list_alerts
Read onlyA project's alert rules, with their ids and whether each is on.
- When
- Before adding, changing or deleting a rule.
- Cost
- Not metered.
| Input | Type and meaning |
|---|---|
project_idrequired |
integer. Project id from list_projects. |
What comes back
{
"alerts": [
{
"id": 1,
"type": "moved_down",
"threshold": 3,
"enabled": true,
"rule": "Moved down 3+ places",
"list": null
},
{
"id": 2,
"type": "entered_top3",
"threshold": null,
"enabled": true,
"rule": "Entered the top 3",
"list": null
}
]
}
update_alert
Changes dataTurns an alert rule on or off, or changes how many places it watches for.
- When
- When the person wants a rule paused or changed.
- Cost
- Not metered.
| Input | Type and meaning |
|---|---|
project_idrequired |
integer. Project id from list_projects. |
alert_idrequired |
integer. From list_alerts. |
enabled |
boolean |
threshold |
integer. Places moved, 1-29. Only for moved_up and moved_down. |
What comes back
{
"alert": {
"id": 7,
"type": "moved_down",
"threshold": 5,
"enabled": true,
"rule": "Moved down 5+ places",
"list": "spring"
}
}
get_alert_history
Read onlyThe alerts a project has fired, newest first: date, keyword, the rule and the move, 50 to a page. A position of null means not in the top 30.
- When
- When the person asks what fired.
- Cost
- Not metered.
| Input | Type and meaning |
|---|---|
project_idrequired |
integer. Project id from list_projects. |
page |
integer |
What comes back
{
"events": [
{
"date": "2026-11-07T06:00:00+00:00",
"keyword": "trail running shoes",
"keyword_id": 1,
"rule": "Moved down 2+ places",
"from": 4,
"to": 7
}
],
"page": 1,
"pages": 1,
"total": 9
}
delete_alert
DestructiveDeletes an alert rule.
- When
- Only after confirming with the person, or turn it off with update_alert instead. The tool says so.
- Cost
- Not metered.
| Input | Type and meaning |
|---|---|
project_idrequired |
integer. Project id from list_projects. |
alert_idrequired |
integer. From list_alerts. |
What comes back
{
"deleted": 7
}
list_channels
Read onlyThe Slack channels and webhooks that receive a project's daily alert summary, with their status. The full addresses stay hidden, because they can hold a secret.
- When
- Before adding or testing a channel.
- Cost
- Not metered.
| Input | Type and meaning |
|---|---|
project_idrequired |
integer. Project id from list_projects. |
What comes back
{
"channels": [
{
"id": 1,
"type": "slack",
"host": "hooks.slack.com",
"status": "active",
"last_error": null,
"last_success_at": "2026-11-07T09:00:00+00:00"
},
{
"id": 2,
"type": "webhook",
"host": "hooks.larkfield.example",
"status": "active",
"last_error": null,
"last_success_at": "2026-11-07T09:00:00+00:00"
}
]
}
add_channel
Changes dataSends a project's daily alert summary to a Slack incoming webhook or to any public https address. A webhook's signing secret is shown once, in the reply.
- When
- When the person wants alerts in Slack or in their own system.
- Cost
- Needs a plan or a running trial.
| Input | Type and meaning |
|---|---|
project_idrequired |
integer. Project id from list_projects. |
typerequired |
slack | webhook |
urlrequired |
string. The Slack webhook address, or your own https:// address. |
What comes back
{
"channel": {
"id": 3,
"type": "slack",
"host": "hooks.slack.com",
"status": "active",
"last_error": null,
"last_success_at": null
}
}
test_channel
Changes dataSends a test message to a Slack channel or webhook and says whether it arrived.
- When
- After adding a channel.
- Cost
- Needs a plan or a running trial.
| Input | Type and meaning |
|---|---|
project_idrequired |
integer. Project id from list_projects. |
channel_idrequired |
integer. From list_channels. |
What comes back
{
"ok": true,
"error": null
}
delete_channel
DestructiveStops sending a project's alerts to a Slack channel or webhook.
- When
- Only after confirming with the person. The tool says so.
- Cost
- Not metered.
| Input | Type and meaning |
|---|---|
project_idrequired |
integer. Project id from list_projects. |
channel_idrequired |
integer. From list_channels. |
What comes back
{
"deleted": 3
}
Reports
generate_report
Read onlyMakes the branded PDF report of a project over 7 or 30 days, 30 by default: overview, winners and losers, trend and every keyword. It returns a download link that works for one hour without signing in.
- When
- When the person needs a client report.
- Cost
- Not metered.
| Input | Type and meaning |
|---|---|
project_idrequired |
integer. Project id from list_projects. |
days |
7 | 30. Period covered, 7 or 30 days. Default 30. |
What comes back
{
"url": "https://api.theserpagent.ai/api/reports/1/download?days=30&expires=…&signature=…",
"expires_in_minutes": 60,
"filename": "report-alpinerun.example-2026-11-07.pdf"
}
get_report_branding
Read onlyWhat a project's PDF report carries: company name, colour, whether it has a logo and whether our name is hidden, plus where replies to its scheduled emails go.
- When
- Before making or changing a client's report.
- Cost
- Not metered.
| Input | Type and meaning |
|---|---|
project_idrequired |
integer. Project id from list_projects. |
What comes back
{
"company_name": "Larkfield SEO",
"color": "#1d4ed8",
"reply_to": "reports@larkfield.example",
"hide_branding": true,
"has_logo": true
}
set_report_branding
Changes dataPuts a company name and colour on a project's PDF report, and chooses whether our name is hidden. The company name also goes on the From line of its scheduled emails, and replies go to reply_to; the sending address stays ours. Only the fields sent change, and null clears one.
- When
- When the person wants the report under their own name.
- Cost
- Not metered.
| Input | Type and meaning |
|---|---|
project_idrequired |
integer. Project id from list_projects. |
company_name |
string or null. Up to 80 characters. |
color |
string or null. Hex colour such as #0f766e. |
reply_to |
string or null. Where replies to scheduled report emails go. Without one, a reply reaches our address and not the agency. |
hide_branding |
boolean. True hides "Powered by TheSerpAgent". |
What comes back
{
"company_name": "Larkfield SEO",
"color": "#1d4ed8",
"reply_to": "reports@larkfield.example",
"hide_branding": true,
"has_logo": false
}
set_report_logo
Changes dataPuts a logo on a project's PDF report, from a public image address or base64: PNG or JPEG, up to 512 KB.
- When
- When the person gives a logo for the report.
- Cost
- Not metered.
| Input | Type and meaning |
|---|---|
project_idrequired |
integer. Project id from list_projects. |
image_url |
string. Public https:// address of the image. |
image_base64 |
string. The image itself as base64, instead of image_url. |
What comes back
{
"company_name": "Larkfield SEO",
"color": "#1d4ed8",
"hide_branding": true,
"has_logo": true
}
remove_report_logo
Changes dataTakes the logo off a project's PDF report.
- When
- When the person wants the logo gone.
- Cost
- Not metered.
| Input | Type and meaning |
|---|---|
project_idrequired |
integer. Project id from list_projects. |
What comes back
{
"company_name": "Larkfield SEO",
"color": "#1d4ed8",
"hide_branding": true,
"has_logo": false
}
list_report_schedules
Read onlyThe PDF reports a project emails on a schedule: how often, to whom and when the next one goes out.
- When
- Before adding or removing a schedule.
- Cost
- Not metered.
| Input | Type and meaning |
|---|---|
project_idrequired |
integer. Project id from list_projects. |
What comes back
{
"schedules": [
{
"id": 1,
"frequency": "weekly",
"recipients": [
"marketing@alpinerun.example"
],
"next_run_at": "2026-11-09T07:00:00+00:00"
}
]
}
create_report_schedule
Changes dataEmails a project's PDF report daily, weekly or monthly to up to 5 people, at 07:00 UTC. A project can hold up to 5 schedules.
- When
- When the person wants a report sent on its own. It writes to the addresses given, so confirm them first. The tool says so.
- Cost
- Needs a plan or a running trial.
| Input | Type and meaning |
|---|---|
project_idrequired |
integer. Project id from list_projects. |
frequencyrequired |
daily | weekly | monthly |
recipientsrequired |
array of string. Up to 5 email addresses. |
What comes back
{
"schedule": {
"id": 2,
"frequency": "weekly",
"recipients": [
"marketing@summitbikes.example"
],
"next_run_at": "2026-11-09T07:00:00+00:00"
}
}
delete_report_schedule
DestructiveStops emailing a scheduled report.
- When
- Only after confirming with the person. The tool says so.
- Cost
- Not metered.
| Input | Type and meaning |
|---|---|
project_idrequired |
integer. Project id from list_projects. |
schedule_idrequired |
integer. From list_report_schedules. |
What comes back
{
"deleted": 2
}
Summaries
get_summary
Read onlyThe latest plain-English summary of a project, and whether it is from today. Summaries are written automatically from the ranking figures, not by AI.
- When
- Before writing a new one.
- Cost
- Not metered.
| Input | Type and meaning |
|---|---|
project_idrequired |
integer. Project id from list_projects. |
What comes back
{
"summary": {
"text": "17 of your 18 tracked keywords rank in the top 30, averaging position 8.47, worse than 8.35 before. 3 are in the top 3 and 11 in the top 10. Biggest gain: \"trail running poles\", up 1 to position 3. Biggest drop: \"trail running shoes\", down 3 to position 7. Your share of visibility is 15.5%; trailhouse.example leads with 17.1%. Google shows an AI Overview for 9 of your keywords and cites you in 4.",
"source": "template",
"date": "2026-11-07"
},
"up_to_date": true
}
write_summary
Changes dataWrites today's plain-English summary of a project, automatically from its ranking figures. No AI writes it on our side, so for a fuller write-up read get_overview and get_changes and write it yourself in the conversation; that text does not go into PDF reports. A summary already written today comes back as it is.
- When
- When the person wants the state of a project in words.
- Cost
- No allowance. Needs a plan or a running trial.
| Input | Type and meaning |
|---|---|
project_idrequired |
integer. Project id from list_projects. |
What comes back
{
"summary": {
"text": "17 of your 18 tracked keywords rank in the top 30, averaging position 8.47, worse than 8.35 before. 3 are in the top 3 and 11 in the top 10. Biggest gain: \"trail running poles\", up 1 to position 3. Biggest drop: \"trail running shoes\", down 3 to position 7. Your share of visibility is 15.5%; trailhouse.example leads with 17.1%. Google shows an AI Overview for 9 of your keywords and cites you in 4.",
"source": "template",
"date": "2026-11-07"
},
"cached": false
}
Where this list comes from
The names and inputs are read from the server's own tool list. Each answer is the tool's real output from a test copy of the server, on a fictional agency's data, with long lists shortened. The tool hints are the server's: readOnlyHint and destructiveHint.