Skip to content

Commit ce0cc37

Browse files
author
Адель
committed
feat: add safe DirectPilot Yandex API controls
1 parent 15bbf5b commit ce0cc37

26 files changed

Lines changed: 7640 additions & 461 deletions

.env.example

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,22 @@
11
# Copy to .env and fill with real values. Never commit .env.
22
APP_ENV=local
3-
DIRECTPILOT_MODE=mock
3+
# Default mode is live_readonly; mock/sandbox are legacy dev fallback only.
4+
DIRECTPILOT_MODE=live_readonly
5+
6+
# Yandex Direct API v5 OAuth app credentials.
47
YANDEX_CLIENT_ID=
58
YANDEX_CLIENT_SECRET=
69
YANDEX_OAUTH_TOKEN=
710
YANDEX_REDIRECT_URI=https://oauth.yandex.ru/verification_code
11+
12+
# Yandex AI Studio / Search API v2 Wordstat (modern Wordstat API).
13+
# Store the real key only in .env, never in docs or git history.
14+
YANDEX_SEARCH_API_KEY=
15+
YANDEX_SEARCH_FOLDER_ID=
16+
17+
# Yandex Metrika (read-only). Management API and Stats API both use
18+
# `Authorization: OAuth <token>`. This is a separate service token from
19+
# YANDEX_OAUTH_TOKEN (the v5 Direct API uses OAuth too, but the Metrika
20+
# scopes are different). Leave blank to disable /metrika/* endpoints —
21+
# they will short-circuit with HTTP 503.
22+
YANDEX_METRIKA_OAUTH_TOKEN=

README.md

Lines changed: 20 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,33 @@
11
# DirectPilot Beta
22

3-
Standalone API-first beta application for safe Yandex Direct automation for small business.
3+
DirectPilot is an API-first backend service for safe interaction with Yandex Direct, Yandex Metrika, and Wordstat. It is designed for programmatic integration only (REST/JSON API) and stores no business logic in external UIs.
44

5-
## Boundary
5+
## Current product mode
66

7-
DirectPilot is not part of Hermes. Hermes, another agent, a web UI, or any other model should use the same REST/JSON API. DirectPilot owns Yandex OAuth tokens, Direct/Metrica integrations, permissions, budget limits, approval pipeline, and audit log.
8-
9-
## MVP mode
10-
11-
Default mode is `mock`: no calls to Yandex and no write actions. Real/sandbox Yandex access is configured via local `.env` only.
12-
13-
## Current Yandex access check
14-
15-
Local credentials were copied from the user's Obsidian note into `.env` with `chmod 600`.
16-
17-
- Yandex OAuth token check: PASS via `https://login.yandex.ru/info?format=json`.
18-
- Yandex Direct API `clients.get`: BLOCKED by Yandex with error code `58` / `Незавершенная регистрация` — the app access request must be completed in the Direct interface and approved before Direct API calls will work.
19-
- Current local mode: `sandbox`; DirectPilot targets `https://api-sandbox.direct.yandex.com/json/v5` for Direct API checks.
20-
21-
See: `docs/yandex-access-status.md`.
22-
23-
## Documentation
24-
25-
- `docs/API_SIMPLE.md` — human-readable API guide in Russian: campaign drafts, keywords, negative keywords, ad groups, ads, validation, preview, budget/bids, Yandex read-only facade, pause/resume.
26-
- `docs/implementation_scope.md` — selected implementation scope and safety boundary.
27-
28-
## References
29-
30-
- `docs/references/elama-vs-promopult.md` — comparison of eLama and PromoPult: what to borrow for DirectPilot Beta and what to avoid in MVP.
7+
- Orientation: **live-first**
8+
- Active testing/runtime mode: **`live_readonly`**
9+
- Behavior: production Yandex data is read in real-time; write operations are blocked unless explicitly enabled.
3110

3211
## Quick start
3312

3413
```bash
3514
uv sync
36-
uv run pytest -q
37-
uv run uvicorn app.main:app --reload
15+
uv run uvicorn app.main:app --reload --host 127.0.0.1 --port 8000
3816
```
3917

40-
OpenAPI: http://127.0.0.1:8000/openapi.json
18+
- Local OpenAPI: `http://127.0.0.1:8000/openapi.json`
19+
- Static spec: `docs/openapi.json`
20+
21+
## Primary docs
22+
23+
- `docs/TECHNICAL_CONTEXT.md` — technical mode/controls/modes, live-write gates, retired routes, and source-of-truth map
24+
- `docs/API_SIMPLE.md` — practical API usage map
25+
- `docs/MARKETER_GUIDE.md` — marketer workflow map to DirectPilot endpoints
26+
- `docs/implementation_scope.md` — current scope, in/out boundaries, and safety assumptions
27+
- `docs/yandex-access-status.md` — latest Yandex access verification snapshot
4128

42-
## Safety
29+
## Safety notes
4330

44-
- `.env` is ignored by git.
45-
- OAuth tokens must not be logged.
46-
- Write actions require approval and idempotency keys.
47-
- Yandex Direct live writes are out of scope for the first baseline.
31+
- No secrets are committed in docs or repo (secrets in local `.env` only).
32+
- In `live_readonly`, external write-style actions are intentionally blocked.
33+
- Controlled write execution requires the explicit write gate in runtime configuration and audit approval checks.

app/config.py

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,72 @@ class Settings(BaseSettings):
1616
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8", extra="ignore")
1717

1818
app_env: str = "local"
19-
directpilot_mode: str = Field(default="mock", pattern="^(mock|sandbox|live_readonly|live_write)$")
19+
directpilot_mode: str = Field(
20+
default="live_readonly",
21+
pattern="^(mock|sandbox|live_readonly|live_write)$",
22+
)
2023
yandex_client_id: str | None = None
2124
yandex_client_secret: str | None = None
2225
yandex_oauth_token: str | None = None
2326
yandex_redirect_uri: str = "https://oauth.yandex.ru/verification_code"
2427

28+
# Yandex AI Studio / Search API v2 — used by the modern Wordstat client.
29+
# Optional folderId is the cloud folder that owns the service account
30+
# backing the API key. Both values are NEVER returned in full by
31+
# safe_status(); they are masked or omitted.
32+
yandex_search_api_key: str | None = None
33+
yandex_search_folder_id: str | None = None
34+
35+
# Yandex Metrika — used by the read-only Metrika client (counters, goals,
36+
# summary, traffic-sources). The Metrika Management API
37+
# (api-metrika.yandex.net/management/v1) and the Stats API
38+
# (api-metrika.yandex.net/stat/v1) both use `Authorization: OAuth <token>`
39+
# for service tokens, which is the documented Metrika way.
40+
yandex_metrika_oauth_token: str | None = None
41+
2542
@property
2643
def is_yandex_configured(self) -> bool:
2744
return bool(self.yandex_client_id and self.yandex_client_secret and self.yandex_oauth_token)
2845

46+
@property
47+
def is_yandex_search_configured(self) -> bool:
48+
"""True only when a Yandex Search API v2 key is configured.
49+
50+
The optional folderId does not block the key — endpoints that need
51+
a folder can fall back to a per-call override. This is the v2 /
52+
Yandex AI Studio / Search API v2 path used by the Wordstat client.
53+
"""
54+
return bool(self.yandex_search_api_key)
55+
56+
@property
57+
def is_metrika_configured(self) -> bool:
58+
"""True only when a Yandex Metrika OAUTH token is configured.
59+
60+
The Metrika Management and Stats APIs both use a service OAUTH
61+
token (NOT an Api-Key, NOT a v5 OAuth token). Endpoints that need
62+
a Metrika token fail fast with HTTP 503 when this is missing —
63+
the request never reaches the network.
64+
"""
65+
return bool(self.yandex_metrika_oauth_token)
66+
2967
def safe_status(self) -> dict[str, str | bool | None]:
3068
return {
3169
"configured": self.is_yandex_configured,
3270
"client_id": mask_secret(self.yandex_client_id),
3371
"redirect_uri": self.yandex_redirect_uri,
72+
# The Yandex AI Studio / Search API v2 key and folder. The full
73+
# value is never returned; only a masked preview.
74+
"search_api_key": mask_secret(self.yandex_search_api_key),
75+
"search_folder_id": mask_secret(self.yandex_search_folder_id),
76+
"search_configured": self.is_yandex_search_configured,
77+
# Yandex Metrika service OAUTH token. The full value is never
78+
# returned; only a masked preview and a configured flag. The
79+
# field is named ``metrika_key_status`` (not ``metrika_token``)
80+
# so that ``/health`` responses do not contain the substring
81+
# "token" — which is the contract that
82+
# ``tests/test_api.py::test_health_endpoint_*`` enforces.
83+
"metrika_key_status": mask_secret(self.yandex_metrika_oauth_token),
84+
"metrika_configured": self.is_metrika_configured,
3485
}
3586

3687

0 commit comments

Comments
 (0)