epovest
The official Python SDK for the Epovest API.
With Epovest, businesses make AIs recommend them. From the questions their customers ask to the sources that shape the answers, everything is measured, dated and verifiable. And everything leads to action: where to appear, what to fix, and proof of what changed.
The assistants it measures are ChatGPT, Claude, Gemini, Perplexity, Mistral and Grok.
The whole REST API v1 is here, one method per route, generated from the OpenAPI 3.1 description the API serves at epovest.com/docs/openapi.json.
- Synchronous and asynchronous clients, on
httpx. - Typed: every named object of the API is a
TypedDict, and every method says what it answers. - Query parameters are spelled out as keyword arguments, so your editor lists them.
Install
pip install epovest
Python 3.11 and above.
Quickstart
import os
from epovest import Epovest
epovest = Epovest(api_key=os.environ["EPOVEST_API_KEY"])
# What is already measured
trackers = epovest.list_trackers()["trackers"]
# Set up a new measurement, and start it
tracker = epovest.create_tracker(
body={
"title": "Market watch",
"prompts": ["Which GEO tracking solution should I choose?"],
"engines": ["chatgpt", "claude"],
"frequency": "weekly",
"resolution": "hd",
"keywords": [{"keyword": "Epovest", "favorite": True}],
"analysts": ["keyword_presence", "share_of_voice"],
}
)["tracker"]
epovest.start_tracker(tracker["id"])
# Read the series
scores = epovest.get_results(tracker["id"], analyst="keyword_presence")["scores"]
Asynchronous, same names:
from epovest import AsyncEpovest
async with AsyncEpovest(api_key=os.environ["EPOVEST_API_KEY"]) as epovest:
answers = await epovest.get_responses(tracker_id, engine="claude", tone="negative")
Authentication
Every call carries an API key of your account. The owner creates one in the app, at
/account/api-keys; the secret starts with epo_ and is shown once, at creation.
Scopes are chosen at creation: read, which every key has, and write, for creating and changing
things. Each method names the scope it takes in its docstring.
epovest = Epovest(
api_key=os.environ["EPOVEST_API_KEY"],
timeout=30.0,
max_retries=2,
headers={"User-Agent": "acme-reporting/2.1"},
)
The client holds a connection pool: use it as a context manager, or call close()
(aclose() on the asynchronous one) when you are done.
What you can call
70 methods, named after the operation they wrap, and the name is the same one the MCP tool carries:
| Area | Methods |
|---|---|
| Trackers | list_trackers, get_tracker, create_tracker, update_tracker, start_tracker, survey_now, pause_tracker, archive_tracker |
| Results | list_surveys, get_results, get_responses |
| Keyword discovery | list_keyword_discoveries, accept_keyword_discovery, dismiss_keyword_discovery, restore_keyword_discovery |
| Projects and canon | list_projects, create_project, rename_project, get_canon, update_project_canon, archive_project, get_link_targets, set_link_targets |
| Surfaces | list_surfaces, create_surface, update_surface, tick_surface_checklist, add_surface_check, update_surface_check, delete_surface_check, restore_surface_check, delete_surface, restore_surface, convert_surface_to_corroboration |
| Corroborations | list_corroborations, create_corroboration, update_corroboration, verify_corroboration, set_registry_monitoring, archive_corroboration, convert_corroboration_to_surface, list_corroboration_candidates, dismiss_corroboration_candidate |
| Logbook | get_logbook, create_logbook_entry, update_logbook_entry, delete_logbook_entry, restore_logbook_entry |
| Quests | list_quests, create_quest, update_quest, complete_quest, dismiss_quest, reopen_quest |
| Competitor scans | list_competitor_scans, get_competitor_scan, create_competitor_scan, start_competitor_scan, update_competitor_scan |
| Atlas of sources | list_sources, get_source, list_source_channels |
| Account, credits, usage | get_account_settings, update_account_settings, get_credits, topup_credits, get_usage |
| Support | contact_support, list_support_threads, get_support_thread |
Path parameters come first, the query parameters follow as keyword arguments, and a request body
goes in body:
epovest.update_surface(surface_id, body={"languages": ["en", "pt-br"]})
epovest.list_sources(domain="wikipedia.org", sort="aa_claude", per_page=100)
Types
Every named object of the API is exported as a TypedDict, and every method is annotated with what
it answers:
from epovest import Credits, Tracker
def shortfall(credits: Credits) -> int:
return max(0, credits["min_topup_minor"] - credits["available_minor"])
Pagination
GET /sources, GET /trackers/{id}/responses and GET /usage are paginated, and page is
clamped: asking for page 99 of 3 answers page 3. paginate reads that the way the API intends,
and stops on the last page:
for page in epovest.paginate(lambda page: epovest.list_sources(page=page, per_page=100)):
for source in page["sources"]:
print(source["domain"], source["engines"])
Errors
A refused call raises an EpovestError carrying the slug of the refusal. Branch on code, which is
stable, rather than on the message, which is written for a person. Everything the envelope carries
next to the two of the envelope stays readable in body:
from epovest import EpovestError
try:
epovest.start_tracker(tracker_id)
except EpovestError as error:
if error.code == "insufficient_credits":
# The answer carries what a survey costs, what is available, and where to top up.
print(error.body["cost_per_survey_minor"], error.body["available_minor"], error.body["top_up_url"])
raise
A request that never reached Epovest raises an EpovestConnectionError, whose cause carries what
the platform reported.
Retries. A 429 rate_limited is sent again whatever the verb, after the wait the Retry-After
header names: the budget refuses the call before it runs. A network failure or a 5xx is sent again
on reads alone, so a write that may have landed is yours to replay, when you decide it should be.
max_retries sets how many times, and 0 turns it off.
Also available as MCP
The same surface is served as MCP tools at https://mcp.epovest.com/mcp, for agents that speak the
Model Context Protocol: one tool per route, the same keys and the same error envelopes, and the tool
names are the method names of this SDK. See
epovest.com/docs/api.md.
Reference
- API and MCP reference: epovest.com/docs/api.md
- OpenAPI 3.1 description: epovest.com/docs/openapi.json
- Task recipes for an agent: epovest.com/docs/recipes.md
- JavaScript and TypeScript SDK: npmjs.com/package/epovest
- Source of every Epovest SDK: github.com/simafri/epovest-sdk
License
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file epovest-1.0.1.tar.gz.
File metadata
- Download URL: epovest-1.0.1.tar.gz
- Upload date:
- Size: 47.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d4441d3cec1bae9d53491216d0bedaa1affc4d87fa2a4ac399c5895a36e8e069
|
|
| MD5 |
fe24ff5a36c4c3078266bdd84363b120
|
|
| BLAKE2b-256 |
4179f50b57d004be2952cb63c4d763050546761ce86aeb4148cfbd2d6c294db8
|
File details
Details for the file epovest-1.0.1-py3-none-any.whl.
File metadata
- Download URL: epovest-1.0.1-py3-none-any.whl
- Upload date:
- Size: 40.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e8e19f1446792dd9553a0d5bfaf98d34e58dac14a68fc2ef063c2c292a325081
|
|
| MD5 |
83ddc8ac3a8c9d6b05b76df4e87fb4d8
|
|
| BLAKE2b-256 |
684520ecbe6d6d5c6f67db0e1019738e54e2ec0222ad2e8a48c4e345e0282041
|