Metadata-Version: 2.5
Name: epovest
Version: 1.0.1
Summary: The official Python SDK for the Epovest API: measure how AI assistants answer the questions your market asks, and shape those answers.
Project-URL: Homepage, https://epovest.com
Project-URL: Documentation, https://epovest.com/docs/api.md
Project-URL: Source, https://github.com/simafri/epovest-sdk/tree/main/python
Project-URL: Issues, https://github.com/simafri/epovest-sdk/issues
Author-email: Epovest <support@epovest.com>
License-Expression: MIT
License-File: LICENSE
Keywords: ai-visibility,api-client,brand-monitoring,chatgpt,claude,epovest,gemini,generative-engine-optimization,geo,mcp,perplexity,sdk
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: anyio>=4
Requires-Dist: httpx>=0.27
Provides-Extra: dev
Requires-Dist: black==26.5.1; extra == 'dev'
Requires-Dist: datamodel-code-generator==0.74.0; extra == 'dev'
Requires-Dist: mypy>=1.11; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: pyyaml>=6; extra == 'dev'
Description-Content-Type: text/markdown

# epovest

The official Python SDK for the [Epovest](https://epovest.com) 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](https://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

```bash
pip install epovest
```

Python 3.11 and above.

## Quickstart

```python
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:

```python
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.

```python
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`:

```python
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:

```python
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:

```python
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`:

```python
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](https://epovest.com/docs/api.md).

## Reference

- API and MCP reference: [epovest.com/docs/api.md](https://epovest.com/docs/api.md)
- OpenAPI 3.1 description: [epovest.com/docs/openapi.json](https://epovest.com/docs/openapi.json)
- Task recipes for an agent: [epovest.com/docs/recipes.md](https://epovest.com/docs/recipes.md)
- JavaScript and TypeScript SDK: [npmjs.com/package/epovest](https://www.npmjs.com/package/epovest)
- Source of every Epovest SDK: [github.com/simafri/epovest-sdk](https://github.com/simafri/epovest-sdk)

## License

MIT
