yandex-oauth-token
Working with Yandex OAuth (Yandex ID) user-scoped tokens: get one, refresh it non-interactively,
wire it into a service/cron, and diagnose auth failures (esp. 403 access_denied on Metrika).
Full flows, curl examples, diagnostics table and sources → reference.md.
Core facts
- Auth header for Yandex Metrika API is
Authorization: OAuth <token>— NOTBearer. - Endpoints: authorize
https://oauth.yandex.ru/authorize, tokenhttps://oauth.yandex.ru/token. - Scope: Metrika Logs API (create/download logrequests) needs only
metrika:read.metrika:writeis for editing counters / uploading data. The token's account must also have at least guest read access to the specific counter. client_credentialsdoes NOT work for user data — you cannot mint a user token from client_id + client_secret alone. User consent (a browser step) is always required once.
Getting a token (happy path)
Prefer authorization-code flow for anything unattended (it yields a refresh_token):
- One-time, in a browser logged in as the account with access to the counter:
https://oauth.yandex.ru/authorize?response_type=code&client_id=<CLIENT_ID>→ short-lived single-usecode(~10 min TTL, shown on-page or in redirect?code=). - Exchange server-side (needs
client_secret):
→ JSONcurl -sS -X POST 'https://oauth.yandex.ru/token' \ --data-urlencode 'grant_type=authorization_code' \ --data-urlencode 'code=<CODE>' \ --data-urlencode 'client_id=<CLIENT_ID>' \ --data-urlencode 'client_secret=<CLIENT_SECRET>'access_token,refresh_token,expires_in.
Quick alternative: implicit flow (response_type=token) is fastest but gives no
refresh_token → must be regenerated by hand each time. Details in reference.md.
Helper script
scripts/yandex-oauth.sh automates the code flow — two commands:
OAUTH_PREFIX=OAUTH_METRIC ENV_FILE=.env.local scripts/yandex-oauth.sh authorize-url # open in browser → grab ?code=
OAUTH_PREFIX=OAUTH_METRIC ENV_FILE=.env.local scripts/yandex-oauth.sh exchange <CODE> # code → token+refresh, saved
It reads <PREFIX>_CLIENT_ID/_CLIENT_SECRET from ENV_FILE (keyed by OAUTH_PREFIX, default OAUTH)
and writes <PREFIX>_TOKEN + <PREFIX>_REFRESH_TOKEN back into the same file. refresh reuses the
stored refresh_token and persists a rotated refresh_token if Yandex returns one; check [COUNTER_ID]
probes validity. Secrets are never printed (only lengths). All four subcommands + config → reference.md.
Refreshing (non-interactive, cron)
curl -sS -X POST 'https://oauth.yandex.ru/token' \
--data-urlencode 'grant_type=refresh_token' \
--data-urlencode 'refresh_token=<REFRESH_TOKEN>' \
--data-urlencode 'client_id=<CLIENT_ID>' \
--data-urlencode 'client_secret=<CLIENT_SECRET>'
- Yandex may ROTATE the refresh_token — the response can carry a NEW
refresh_token. You MUST persist it (overwrite the stored one) or the next refresh fails. - Always
--data-urlencode, never bare-d— tokens can contain+ / =, which raw form-encoding corrupts.
Diagnosing failures
403 access_deniedon a Metrika call = token's account lost access to the counter, OR token was revoked (OAuth app deleted / password changed), OR expired. It is NOT a scope typo — scope is fixed at issue-time. Fix: regenerate/refresh from an account that currently has access. Trap: "I have access" refers to a login; the token belongs to whatever account originally authorized it, which may differ.401= usually missing/invalid token or wrong scope.- Validity test (never print the token):
curl -sS -o /dev/null -w '%{http_code}' -H "Authorization: OAuth $TOK" https://api-metrika.yandex.net/management/v1/counter/<COUNTER_ID>→200valid,403access lost. - Yandex recommends refreshing long-lived tokens roughly every ~3 months; trust the returned
expires_in.
Safety (non-negotiable)
- Never commit real tokens/secrets. Keep secret env files (
.env.local) gitignored with empty tracked placeholders. - Never print/echo/log tokens — to logs, chat, or notifications. Mask to length only. On failure,
alert with
error/error_descriptiononly. - Use placeholders (
<CLIENT_ID>,<CLIENT_SECRET>,<REFRESH_TOKEN>) in all docs/examples.