# 핀노트 API · CLI - 기본 주소: `https://pinnote.itemview.co.kr` - 응답 형식 - 성공: `{"ok":true,"data":...}` (목록은 `meta`도 붙음) - 실패: `{"ok":false,"error":{"code":"...","message":"..."}}` + HTTP 상태 - 시간대: Asia/Seoul (`YYYY-MM-DD HH:MM:SS`) ## 1. 개발자 REST API v1 토큰은 관리 화면 **API 토큰** 메뉴에서 만든다. 원문은 만들 때 한 번만 보인다. ```bash export PN=https://pinnote.itemview.co.kr export PN_TOKEN=pn_여기에_토큰 ``` - 인증: `Authorization: Bearer $PN_TOKEN` (또는 `X-Pinnote-Token`) - 토큰 없음·틀림 → `401 UNAUTHORIZED`. 읽기 전용 토큰으로 쓰기 → `403 FORBIDDEN`. - 쓰기 본문은 JSON이다. `"author":"이름"`을 넣으면 히스토리 작업자가 `API:토큰이름 (이름)`으로 남는다. ### 사이트 목록 ```bash curl -s -H "Authorization: Bearer $PN_TOKEN" $PN/api/v1/sites ``` ### 의견 목록 ```bash curl -s -H "Authorization: Bearer $PN_TOKEN" "$PN/api/v1/items?site=pk_a8d81b33751c7fc8e2120547&status=open&per_page=20" ``` | 파라미터 | 값 | |---|---| | `site` | 사이트 id 또는 사이트 키 | | `status` | `open`(NEW·TRIAGED·IN_PROGRESS·NEEDS_INFO·REOPENED), `resolved`, `active`(open+resolved), `closed`, `all`, 또는 `NEW,TRIAGED` | | `kind` | `BUG`(오류) `DESIGN`(디자인) `COPY`(문구) `IDEA`(아이디어) `UX`(불편), 쉼표로 여러 개 | | `anchor_status` | `ATTACHED` `RELOCATED` `MISSING` `ARCHIVED` | | `page_key` | 화면 키, 예: `/t/:token` (URL 인코딩) | | `assignee` | 멤버 id 또는 `none` | | `label`, `q` | 라벨 / 본문·요소 이름·번호 검색 | | `sort` | `new`(기본) `old` `votes` `updated` `page` | | `page`, `per_page` | 페이지 나누기(최대 200) | 응답의 `meta`: `{total, page, per_page, pages}` ### 의견 상세 (스냅샷·문맥·댓글·히스토리) ```bash curl -s -H "Authorization: Bearer $PN_TOKEN" $PN/api/v1/items/12 ``` 주요 필드 - 기본: `site`, `page.key`, `url`, `kind`, `status`, `priority`, `body`, `reporter`, `votes`, `labels`, `resolution{note,version,ref,at}` - `element`: `label`, `anchor_status`, `ui`, `id`, `selector`, `dom_path`, `tag`, `role`, `accessible_name`, `text`, `landmark`, `position_ratio`, `miss_count` - `snapshot`: `html`, `style`, `rect`, `shots[].url` (서명 URL, 7일) - `context`: `app_version`, `viewport`, `device`, `lang`, `js_errors`, `custom` - `comments[]`, `history[]` (`at`, `action`, `from`, `to`, `note`, `actor{type,name}`) - 링크: `admin_url`, `focus_url` (실제 페이지에서 요소를 깜빡여 보여 주는 주소) ### 댓글 달기 ```bash curl -s -X POST -H "Authorization: Bearer $PN_TOKEN" -H 'Content-Type: application/json' \ -d '{"body":"재현했습니다. 오늘 고칠게요","author":"claude"}' $PN/api/v1/items/12/comments ``` ### 상태 바꾸기 ```bash # 고치는 중 curl -s -X POST -H "Authorization: Bearer $PN_TOKEN" -H 'Content-Type: application/json' \ -d '{"status":"IN_PROGRESS","author":"claude"}' $PN/api/v1/items/12/status # 해결: note(무엇을 어떻게 고쳤는지)가 없으면 422 RESOLUTION_REQUIRED curl -s -X POST -H "Authorization: Bearer $PN_TOKEN" -H 'Content-Type: application/json' \ -d '{"status":"RESOLVED","note":"주문 버튼 onClick 누락을 고치고 로딩 표시를 넣었습니다","version":"1.4.2","ref":"a1b2c3d"}' \ $PN/api/v1/items/12/status # 안 고침(사유 필수) / 중복(원본 번호 필수) / 다시 열기 curl -s -X POST -H "Authorization: Bearer $PN_TOKEN" -H 'Content-Type: application/json' -d '{"status":"WONT_FIX","note":"정책상 유지"}' $PN/api/v1/items/13/status curl -s -X POST -H "Authorization: Bearer $PN_TOKEN" -H 'Content-Type: application/json' -d '{"status":"DUPLICATE","duplicate_of":12}' $PN/api/v1/items/14/status curl -s -X POST -H "Authorization: Bearer $PN_TOKEN" -H 'Content-Type: application/json' -d '{"status":"REOPENED","note":"아이폰에서는 그대로"}' $PN/api/v1/items/12/status ``` 허용되는 이동 | 지금 | 바꿀 수 있는 상태 | |---|---| | NEW | TRIAGED, IN_PROGRESS, NEEDS_INFO, RESOLVED, WONT_FIX, DUPLICATE | | TRIAGED / IN_PROGRESS / NEEDS_INFO / REOPENED | (서로) + RESOLVED, WONT_FIX, DUPLICATE | | RESOLVED | VERIFIED, REOPENED | | VERIFIED / WONT_FIX / DUPLICATE | REOPENED | 오류 코드 - 이동 규칙 위반: `409 BAD_TRANSITION` - 같은 상태로 바꿈: `409 SAME_STATUS` - 동시에 다른 사람이 바꿈: `409 CONFLICT` - 필수 문구 없음: `422 RESOLUTION_REQUIRED`, `422 REASON_REQUIRED` - 중복 원본 번호 문제: `422 DUPLICATE_OF_REQUIRED` ### 중요도·종류·담당자·라벨 ```bash curl -s -X PATCH -H "Authorization: Bearer $PN_TOKEN" -H 'Content-Type: application/json' \ -d '{"priority":"HIGH","kind":"BUG","assignee":"홍길동","labels":["결제","모바일"]}' $PN/api/v1/items/12 ``` ### 다시 연결 ```bash # 새 선택자를 직접 지정. 다음 방문 때 위젯이 확인한다. curl -s -X POST -H "Authorization: Bearer $PN_TOKEN" -H 'Content-Type: application/json' \ -d '{"ui":"order.submit","note":"버튼을 data-ui로 교체"}' $PN/api/v1/items/12/relink # 본문을 비우면 브라우저 재지정 링크(24시간)를 준다. 열고 새 요소를 누르면 연결된다. curl -s -X POST -H "Authorization: Bearer $PN_TOKEN" -H 'Content-Type: application/json' -d '{}' $PN/api/v1/items/12/relink ``` ### 사라진 요소 ```bash curl -s -H "Authorization: Bearer $PN_TOKEN" "$PN/api/v1/missing?site=1" curl -s -H "Authorization: Bearer $PN_TOKEN" $PN/api/v1/anchors/7 # 찾는 방법·스냅샷·요소 히스토리·relink_url # [수정으로 해결됨]: 열린 의견을 모두 RESOLVED로(note 필수), 요소는 보관 curl -s -X POST -H "Authorization: Bearer $PN_TOKEN" -H 'Content-Type: application/json' \ -d '{"note":"쿠폰 배너를 없애고 주문 화면으로 옮겼습니다"}' $PN/api/v1/anchors/7/fixed # [보관] curl -s -X POST -H "Authorization: Bearer $PN_TOKEN" -H 'Content-Type: application/json' -d '{"note":"실험 화면 종료"}' $PN/api/v1/anchors/7/archive ``` `missing` 응답의 `normal_after_fix: true`는 열린 의견이 없다는 뜻이다(해결 뒤 사라진 정상 경우). ### 내보내기 (AI에게 넘기기 좋은 형태) ```bash # 마크다운: 사이트 → 화면 → 요소(선택자들·텍스트) → 의견. status 기본값은 active(열림+해결됨) curl -s -H "Authorization: Bearer $PN_TOKEN" "$PN/api/v1/export?site=1&status=open" > pinnote.md # JSON: 같은 구조 curl -s -H "Authorization: Bearer $PN_TOKEN" "$PN/api/v1/export?site=1&status=open&format=json" ``` 마크다운 모양 ```markdown ## 사이트: 뉴팅 데모 (demo) — imgx.kr ### 화면 `/t/:token` — 테이블 화면 (3건) 예시 주소: https://imgx.kr/t/abc123 #### 요소: "주문하기" 버튼 `[data-ui="order.submit"]` - 선택자: `[data-ui="order.submit"]` · `main > form > button.primary` - DOM 경로: `html > body > main:nth-of-type(1) > form:nth-of-type(1) > button:nth-of-type(2)` - 태그/역할: button / button · 접근성 이름: "주문하기" - 텍스트: "주문하기" - 근처 기준: h2 "장바구니" - 요소 상태: ATTACHED (그대로 찾음) · 마지막으로 찾음 2026-09-16 14:00:00 - **#12** [오류 · HIGH] 상태 NEW · 공감 3 · 작성 홍길동 · 2026-09-16 13:20:00 · 버전 1.4.1 · 화면 390x844@3 > 눌러도 반응이 없어요 - 댓글(API:agent (claude)): 재현했습니다 ``` ### 통계 ```bash curl -s -H "Authorization: Bearer $PN_TOKEN" "$PN/api/v1/stats?site=1" ``` ## 2. CLI (서버에서) ```bash cd /home/webs/pinnote php bin/pinnote.php sites php bin/pinnote.php list --site=pk_a8d81b33751c7fc8e2120547 --status=open # --kind=BUG --page=/t/:token --anchor=MISSING --q=검색 --limit=50 php bin/pinnote.php show 12 # 사람이 읽는 형태 (--json 은 REST 상세와 같은 JSON) php bin/pinnote.php comment 12 "재현했습니다" --as=claude php bin/pinnote.php resolve 12 "주문 버튼 onClick 누락을 고쳤습니다" --version=1.4.2 --ref=a1b2c3d --as=claude php bin/pinnote.php status 13 WONT_FIX "정책상 유지" php bin/pinnote.php status 14 DUPLICATE --dup=12 php bin/pinnote.php missing --site=1 php bin/pinnote.php export --site=1 --status=open > pinnote.md # --format=json ``` - 실패하면 종료 코드 2와 `[코드] 메시지`를 낸다. 예: `resolve`에 문구가 없으면 `[RESOLUTION_REQUIRED]`. - `--as` 이름은 히스토리에 `CLI:이름`으로 남는다. ### AI 에이전트 작업 순서 예 1. `php bin/pinnote.php export --site=1 --status=open > /tmp/pinnote.md`를 읽는다. 2. 요소마다 선택자(`data-ui` 우선)와 텍스트로 코드를 찾아 고친다. 3. 고친 건마다 `resolve 번호 "무엇을 어떻게 고쳤는지" --version=...`을 실행한다. 4. 요소가 사라졌거나 이름이 바뀌었다면 `missing`으로 확인하고, `POST /api/v1/items/{id}/relink {"ui":"새이름"}`이나 `anchors/{id}/fixed`로 정리한다. ## 3. 위젯 API (참고, 위젯 전용) - 브라우저 위젯만 쓰는 공개 API다. 쿠키를 쓰지 않는다. - 본문은 JSON 문자열을 `text/plain`으로 보낸다(preflight를 줄이려고). - 모든 요청에 `site`(공개 키)가 필요하고 **Origin 헤더가 사이트 허용 도메인**이어야 한다(아니면 `403 ORIGIN_REQUIRED/ORIGIN_DENIED`). - `init`·`report`·`item`·`relink`는 본문의 `url` 호스트도 검사한다(`403 URL_DENIED`). | 동작 | 본문 | 설명 | |---|---|---| | `POST /api/w/init` | `url, title, rk, visit, focus?, relink?` | 화면 키, 설정, 이 화면의 앵커(찾는 방법·check)와 의견(작성·처리 시각은 넣지 않음), 포커스·재지정 정보 | | `POST /api/w/item` | `url, title, rk, name, kind, body, uid, anchor_id?, page_level?, loc, snap, ctx` | 의견 저장(201). 같은 `uid`면 기존 의견을 돌려줌(200, `duplicate:true`). 응답의 `shot_token`으로 캡처 업로드 | | `POST /api/w/shot` | `token, data`(data:image/jpeg;base64) | 요소 캡처(300KB 이하) | | `POST /api/w/report` | `url, visit, results[{id, found: primary/fallback/none, method, score, loc}]` | 앵커 확인 결과 묶음 | | `POST /api/w/vote` | `rk, item_id, on` | 공감 토글 | | `POST /api/w/comment` / `thread` | `rk, name, item_id, body` / `item_id` | 댓글 달기 / 스레드 | | `POST /api/w/relink` | `token, url, loc, snap` | 재지정 모드 연결(서명 토큰 필요) | - 보고자 알림(`notices`)·보고자 확인(`/api/w/verify`)은 없앴다(처리 시점 추적 방지). `verify`는 `404 NOT_FOUND`. 위젯 응답에는 의견·댓글의 시각 필드가 없다. 시간당 제한(IP 해시 기준) - 의견·캡처·다시 연결: 사이트 `rate_per_hour`(기본 60) - 댓글: ×2 - 공감: ×4 - init·report·thread: 1200 - 사이트 전체: max(2000, 한도×40) - 넘으면 `429 RATE_LIMITED` 크기 제한 - 의견 본문 JSON 96KB, 내용 2000자 - 캡처 420KB - 나머지 8~64KB - 넘으면 `413 TOO_LARGE`, `422 TOO_LONG`