핀노트 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 토큰 메뉴에서 만든다. 원문은 만들 때 한 번만 보인다.
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:토큰이름 (이름)으로 남는다.
사이트 목록
curl -s -H "Authorization: Bearer $PN_TOKEN" $PN/api/v1/sites
의견 목록
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}
의견 상세 (스냅샷·문맥·댓글·히스토리)
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_countsnapshot:html,style,rect,shots[].url(서명 URL, 7일)context:app_version,viewport,device,lang,js_errors,customcomments[],history[](at,action,from,to,note,actor{type,name})- 링크:
admin_url,focus_url(실제 페이지에서 요소를 깜빡여 보여 주는 주소)
댓글 달기
curl -s -X POST -H "Authorization: Bearer $PN_TOKEN" -H 'Content-Type: application/json' \
-d '{"body":"재현했습니다. 오늘 고칠게요","author":"claude"}' $PN/api/v1/items/12/comments
상태 바꾸기
# 고치는 중
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
중요도·종류·담당자·라벨
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
다시 연결
# 새 선택자를 직접 지정. 다음 방문 때 위젯이 확인한다.
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
사라진 요소
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에게 넘기기 좋은 형태)
# 마크다운: 사이트 → 화면 → 요소(선택자들·텍스트) → 의견. 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"
마크다운 모양
## 사이트: 뉴팅 데모 (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)): 재현했습니다
통계
curl -s -H "Authorization: Bearer $PN_TOKEN" "$PN/api/v1/stats?site=1"
2. CLI (서버에서)
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 에이전트 작업 순서 예
php bin/pinnote.php export --site=1 --status=open > /tmp/pinnote.md를 읽는다.- 요소마다 선택자(
data-ui우선)와 텍스트로 코드를 찾아 고친다. - 고친 건마다
resolve 번호 "무엇을 어떻게 고쳤는지" --version=...을 실행한다. - 요소가 사라졌거나 이름이 바뀌었다면
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