# Yandex OAUTH Token

> Obtain, refresh, wire into a service and diagnose Yandex OAuth (Yandex ID) tokens — Metrika Logs API auth, authorization-code + refresh_token flow, non-interactive cron refresh, 403 access_denied triage. Триггеры RU — «яндекс токен», «yandex oauth», «metrika токен», «refresh token яндекс», «обновить яндекс токен», «токен протух 403 access_denied». EN — «yandex oauth token», «metrika logs api token», «refresh yandex token», «403 access_denied», «authorization code flow yandex».

- Skill: `xakki/yandex-oauth-token` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add xakki/yandex-oauth-token`
- Raw SKILL.md: https://api.skillmd.com/api/skills/xakki/yandex-oauth-token/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: Xakki (https://skillmd.com/u/xakki)
- Updated: 2026-09-21
- Page: https://skillmd.com/skills/xakki/yandex-oauth-token

---


# 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>` — **NOT `Bearer`**.
- Endpoints: authorize `https://oauth.yandex.ru/authorize`, token `https://oauth.yandex.ru/token`.
- **Scope**: Metrika **Logs API** (create/download logrequests) needs only `metrika:read`.
  `metrika:write` is for editing counters / uploading data. The token's account must also have at
  least **guest read** access to the specific counter.
- **`client_credentials` does 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`):

1. 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-use `code` (~10 min TTL, shown on-page or in redirect `?code=`).
2. Exchange server-side (needs `client_secret`):
   ```
   curl -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>'
   ```
   → JSON `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_denied`** on 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>`
  → `200` valid, `403` access 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_description` only.
- Use placeholders (`<CLIENT_ID>`, `<CLIENT_SECRET>`, `<REFRESH_TOKEN>`) in all docs/examples.

