# 온길 — 에이전트 연동 가이드

> 온길(k-datafast)은 방문자의 최초 유입 채널에 결제 매출을 귀속시키는 웹 애널리틱스다.
> 이 문서는 AI 에이전트가 **새 웹 프로젝트를 온길에 등록하고 트래킹을 붙이는 작업**을
> 사람 도움 없이 끝낼 수 있도록 쓴 것이다. 서버 주소: `https://analytics.stonkradar.io`
> (이 문서: `https://analytics.stonkradar.io/llms.txt` · 사람이 읽는 판: `https://analytics.stonkradar.io/docs`)

## 작업 순서 요약

1. `kdf whoami` 로 인증 확인 (안 되면 [인증](#인증) — 토큰은 사람에게 요청)
2. `kdf sites ensure "<프로젝트 이름>" <배포 도메인> --json` 으로 사이트 등록 → `publicId` 획득
3. 모든 페이지 `<head>` 에 트래킹 스크립트 삽입 ([스크립트 설치](#스크립트-설치))
4. (결제가 있으면) 서버에 결제 보고 연동, `sk_` 키는 시크릿 저장소에만 ([결제 연동](#결제-연동))
5. 배포 후 `kdf overview <publicId> --days 1` 로 수집 확인

## 지켜야 할 규칙

- **등록은 `ensure` 로만 한다.** 같은 조직·같은 도메인이면 기존 사이트를 돌려주므로 몇 번 실행해도 안전하다.
  `sites create` 는 중복이면 409 로 실패한다.
- **`sk_...` (서버 API 키) 는 시크릿이다.** 코드·커밋·프론트엔드 번들·로그에 남기지 말고
  배포 플랫폼의 시크릿(예: `wrangler secret put`, Coolify 환경변수, Vercel env)에만 넣는다.
  `ensure --json` 출력에 키가 들어 있으니 출력을 그대로 로그에 찍지 않는다.
- **`publicId` (`kdf_...`) 는 공개값이다.** 스크립트 태그에 그대로 넣어도 된다.
- **사이트 삭제, 토큰 발급, 멤버 초대는 하지 않는다.** 토큰으로는 애초에 할 수 없고, 사람이 할 일이다.
- 도메인은 실제 배포 도메인을 쓴다. `localhost` 는 트래커가 자동으로 수집하지 않는다.

## 인증

CLI `kdf` (Node 24+, 의존성 없음) 가 기본 인터페이스다.

```bash
kdf whoami        # "이름 <이메일> · 역할" 과 서버 주소가 나오면 준비 완료
```

- 설치: 온길 저장소에서 `npm install -g ./cli`
- 인증 정보는 `~/.config/kdf/config.json` 또는 환경변수 `KDF_URL`, `KDF_TOKEN` 에서 읽는다.
- 토큰이 없거나 401 이 나면 **사람에게 조직 API 토큰을 요청**한다.
  사람은 대시보드 `https://analytics.stonkradar.io/orgs` → 조직 → API 토큰에서 `sites:write` 를 포함해 발급한 뒤
  `kdf login --token kpat_... --url https://analytics.stonkradar.io` 로 저장한다.

API 토큰(`kpat_...`) 은 발급한 조직의 사이트에만 접근하고, 실제 권한은 `스코프 ∩ 발급자의 현재 조직 역할` 이다.

| 스코프 | 허용 |
|---|---|
| `sites:read` | 사이트 목록 |
| `sites:write` | 사이트 생성(`ensure`)·수정 |
| `stats:read` | 통계·실시간·메트릭 조회 |
| `keys:read` | **기존** 사이트의 `sk_` 키 조회 (새로 만든 사이트의 키는 이 스코프 없이도 생성 응답에 온다) |

## 사이트 등록

```bash
kdf sites ensure "내 프로젝트" my-project.com --json
```

```json
{
  "created": true,
  "site": {
    "publicId": "kdf_1a2b3c4d5e6f",
    "name": "내 프로젝트",
    "domain": "https://my-project.com",
    "apiKey": "sk_...",
    "org": { "id": "...", "name": "..." },
    "role": "owner"
  }
}
```

- `created: false` 면 이미 있던 사이트다. 이때 `apiKey` 는 `keys:read` 스코프가 있을 때만 들어 있다.
  결제 연동에 키가 필요한데 없으면 사람에게 대시보드의 사이트 → 연동 정보에서 복사해 달라고 요청한다.
- 도메인은 정규화된다: 스킴이 없으면 `https://`, 호스트 소문자, 끝 슬래시 제거. `www.` 는 별개 도메인으로 취급한다.
- 사용자가 여러 조직에 속해 있으면 `--org <orgId>` 가 필요하다 (`kdf orgs` 로 확인). 토큰은 토큰의 조직이 자동 선택된다.

CLI 를 쓸 수 없으면 REST 로 같은 일을 한다:

```bash
curl -X POST https://analytics.stonkradar.io/api/sites/ensure \
  -H "Authorization: Bearer $KDF_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"내 프로젝트","domain":"my-project.com"}'
```

## 스크립트 설치

모든 페이지의 `<head>` 에 한 줄. SPA 라우팅(pushState/popstate)은 자동 추적된다.

```html
<script defer src="https://analytics.stonkradar.io/k.js" data-site="kdf_1a2b3c4d5e6f"></script>
```

| 프레임워크 | 넣는 곳 |
|---|---|
| 정적 HTML · Vite · CRA | `index.html` 의 `<head>` |
| Next.js App Router | `app/layout.tsx` 의 `<head>` 에 `next/script` (`strategy="afterInteractive"`, `data-site` 속성 그대로) |
| Next.js Pages Router | `pages/_document.tsx` 의 `<Head>` 에 일반 `<script defer ...>` |
| Astro | 공통 레이아웃(`src/layouts/*.astro`)의 `<head>` 에 `<script is:inline defer ...>` |
| SvelteKit | `src/app.html` 의 `<head>` |
| Nuxt | `nuxt.config.ts` 의 `app.head.script` 에 `{ src, defer: true, 'data-site': '...' }` |
| Cloudflare Workers (HTML 응답) | 정적 자산이면 HTML 파일의 `<head>`, 문자열로 HTML 을 만들면 그 템플릿의 `<head>` |

선택 속성:

- `data-track-params="user,tab"` — 쿼리 파라미터로 페이지를 구분하는 사이트에서 지정한 파라미터만 경로에 포함
- `data-allow-localhost` — 로컬에서 수집을 시험할 때만 (배포본에는 넣지 않는다)

## 목표(전환) 이벤트

```js
window.kdatafast?.goal('signup');            // 코드에서
```

```html
<button data-kdf-goal="cta_click">시작하기</button>  <!-- 속성만으로 -->
```

## 결제 연동

트래커가 발급한 방문자 ID(`window.kdatafast.visitorId`)를 주문과 연결해 두면 결제 시 그 방문자의 최초 유입 채널에 매출이 귀속된다.
서버 API 는 모두 헤더 `x-api-key: sk_...` 가 필요하므로 **반드시 서버에서** 호출한다.

1. 주문 생성 시 프론트에서 `window.kdatafast?.visitorId` 를 받아 주문과 함께 저장한다.
2. 결제가 확정되는 서버 코드에서 한 번 보고한다 (매핑이 없으면 생성까지 한다):

```ts
await fetch('https://analytics.stonkradar.io/api/payments/paid', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', 'x-api-key': process.env.ONGIL_API_KEY! },
  body: JSON.stringify({
    orderId: 'order-123',
    visitorId,          // 주문 시점에 저장해 둔 값
    amount: 15000,      // 정수. KRW 는 원 단위
    currency: 'KRW',
    provider: 'toss',   // 선택
  }),
});
```

- 결제 전에 매핑만 먼저 등록하려면 `POST /api/payments/link` (같은 본문, `orderId`·`visitorId` 필수).
- PG 웹훅으로 자동 연동도 된다: 포트원 V2 `https://analytics.stonkradar.io/api/webhooks/portone/<publicId>`,
  Stripe `https://analytics.stonkradar.io/api/webhooks/stripe/<publicId>` (Stripe 는 Checkout `metadata.kdf_visitor_id` 에 방문자 ID).
- 랜딩과 앱 도메인이 다르면 이동 링크를 `window.kdatafast.decorate(url)` 로 감싸 방문자를 이어 준다.

## 수집 확인

```bash
kdf overview kdf_1a2b3c4d5e6f --days 1      # 방문자·페이지뷰·채널
kdf summary                                 # 전체 사이트 현재 접속자
```

배포 직후 직접 한 번 방문하면 수 초 안에 잡힌다. 운영자 본인 방문을 빼려면 사이트를
`https://my-project.com/#kdf_exclude` 로 한 번 열면 그 브라우저가 제외된다.

## 오류 대응

| 상태 | 의미 | 할 일 |
|---|---|---|
| 401 | 토큰 없음·만료·폐기 | 사람에게 새 토큰 요청 |
| 403 | 스코프 또는 조직 역할 부족 (예: `sites:write` 없음) | 필요한 스코프를 알려 주고 사람에게 요청 |
| 404 | 그 사이트가 없거나 내 조직 것이 아님 | `kdf sites` 로 publicId 확인 |
| 400 `orgId 를 지정하세요` | 관리하는 조직이 여러 개 | `--org <orgId>` 추가 |
| 409 | `sites create` 중복 | `sites ensure` 사용 |

## API 요약 (토큰으로 호출 가능한 것)

| 메서드 | 경로 | 스코프 |
|---|---|---|
| GET | `/api/sites` | `sites:read` |
| POST | `/api/sites/ensure` | `sites:write` |
| POST | `/api/sites` | `sites:write` |
| PATCH | `/api/sites/:publicId` | `sites:write` |
| GET | `/api/stats/summary` · `/api/stats/overview?siteId=&days=` · `/api/stats/realtime?siteId=` | `stats:read` |
| GET | `/api/metrics/latest?siteId=` | `stats:read` |
| GET | `/api/users/me` | (없음) |

인증 헤더는 `Authorization: Bearer kpat_...`. 그 밖의 API(조직·멤버·토큰·사용자 관리, 사이트 삭제)는 토큰으로 호출할 수 없다.
