{
  "_comment": "트리노트 API 목록 원본 — 정보 API tdoK0901 이 이 파일을 그대로 돌려준다. 기획서 v2·공개 매뉴얼의 표는 이 파일을 기준으로 한다. API 를 더하거나 바꾸면 여기부터.",
  "apiVersion": "v6",
  "updated": "2026-10-11",
  "base": {"prod": "https://treetodo.net/pos", "prodAlt": "https://todo.choeum.com/pos", "dev": "https://dev.posain.com/pos"},
  "firstLine": "불편하거나 없는 기능은 건의 API(tdoK0301, kind=suggest)로 알려 주세요 — 바로 개선에 씁니다. / Missing or awkward features? Tell us via tdoK0301 (kind=suggest).",
  "manual": "/pos/docs/treetodo/ai-api-manual-v1.md",
  "auth": {
    "how": "Authorization: Bearer {access_token} (30분). 만료(reason=TOKEN_EXPIRED)면 refresh 로 새로 받고, TOKEN_REVOKED·TOKEN_INVALID 면 다시 로그인.",
    "accessTtlSec": 1800,
    "refreshTtlDays": 14
  },
  "conventions": {
    "contentType": "사원 API(/td/tdo/wrkr/*) = application/x-www-form-urlencoded · 정문 인증(/td/pa/front/auth/*, /td/front/auth/*) = application/json",
    "result": "resultCode: SUCCESS | FAIL | HUMAN_ACTION_REQUIRED | NOT_SUPPORTED · code(실패 종류) · reason(인증 실패 이유) · next{api,params}(다음에 부를 것) · humanAction{code,message,next,expiresInSec}(사람에게 읽어 줄 일)",
    "readKeys": "사원 API 조회 결과는 대문자 키(UID·TITLE…), 값이 없는 칸은 키가 빠진다"
  },
  "groups": [
    {"name": "계정 — 정문(앱·웹과 같다)", "apis": [
      {"path": "/pos/td/pa/front/auth/email/signup/request", "auth": "none", "method": "POST json", "does": "이메일 인증 가입 ① — 약관 동의 확인 → 인증번호 메일", "params": ["email", "agree_terms=Y", "terms_ver", "privacy_ver", "lang"], "returns": "HUMAN_ACTION_REQUIRED(TERMS_CONSENT → EMAIL_CODE) / 409 EMAIL_EXISTS / 429"},
      {"path": "/pos/td/pa/front/auth/email/signup/confirm", "auth": "none", "method": "POST json", "does": "이메일 인증 가입 ② — 번호 확인 → 계정(검증됨)·토큰", "params": ["email", "code", "password", "terms_ver", "privacy_ver", "device_id", "lang"], "returns": "access_token·refresh_token·nick·is_new_user / 400 CODE_INVALID·CODE_EXPIRED·TERMS_CHANGED / 409"},
      {"path": "/pos/td/pa/front/auth/email/signup", "auth": "none", "method": "POST json", "does": "옛 즉시 가입(메일 인증 없음 · 출시 앱 호환) — 새 코드는 signup/request 를 쓴다", "params": ["email", "password", "device_id"]},
      {"path": "/pos/td/pa/front/auth/email/login", "auth": "none", "method": "POST json", "does": "로그인 — 5회 틀리면 잠금(1~24시간, humanAction ACCOUNT_LOCKED)", "params": ["email", "password", "device_id"], "returns": "access_token·refresh_token / 401(next) / 423 / 429"},
      {"path": "/pos/front/td/front/auth/refresh", "auth": "refresh_token", "method": "POST json", "does": "토큰 갱신(회전) — 옛 refresh 재사용은 침해로 보고 그 기기 세션 전체 폐기", "params": ["refresh_token", "device_id", "dvcNm"]},
      {"path": "/pos/front/td/front/auth/logout", "auth": "Bearer", "method": "POST json", "does": "로그아웃 — 이 기기 세션(refresh·access) 바로 폐기", "params": ["refresh_token"]},
      {"path": "/pos/front/td/front/auth/withdraw", "auth": "Bearer", "method": "POST json", "does": "탈퇴 — 모든 기기 토큰 폐기"},
      {"path": "/pos/td/pa/front/auth/email/reset/request", "auth": "none", "method": "POST json", "does": "비밀번호 재설정 인증번호 메일(=비밀번호 변경) — humanAction PASSWORD_RESET_CODE", "params": ["email", "lang"]},
      {"path": "/pos/td/pa/front/auth/email/reset/confirm", "auth": "none", "method": "POST json", "does": "번호 + 새 비밀번호 → 다른 기기 모두 로그아웃 · 여러 번 틀리면 humanAction CAPTCHA(사람이 웹에서)", "params": ["email", "code", "password"]}
    ]},
    {"name": "계정 — 사원 API(K01)", "apis": [
      {"key": "tdoK0101", "path": "/pos/td/tdo/wrkr/tdoK0101.ajax", "auth": "Bearer", "does": "내 계정 — 닉네임·로그인 수단(이메일·SNS)·기기·약관 동의(바뀌었으면 humanAction TERMS_RECONSENT)"},
      {"key": "tdoK0102", "path": "/pos/td/tdo/wrkr/tdoK0102.ajax", "auth": "Bearer", "does": "닉네임 바꾸기", "params": ["nick"]},
      {"key": "tdoK0104", "path": "/pos/td/tdo/wrkr/tdoK0104.ajax", "auth": "Bearer", "does": "약관 (재)동의 — 사람이 동의한 뒤에만", "params": ["agree=Y", "terms_ver", "privacy_ver"]}
    ]},
    {"name": "노트·할일(K02)", "apis": [
      {"key": "tdoK0202", "path": "/pos/td/tdo/wrkr/tdoK0202.ajax", "auth": "Bearer", "does": "목록·트리·검색·휴지통(delFl=Y)", "params": ["pageUid", "parentUid", "rootOnly", "q", "stat", "type", "impt", "urgn", "favrFl", "hideFl", "treeFl", "mindmapFl", "dueFrom", "dueTo", "delFl", "wsUid"]},
      {"key": "tdoK0203", "path": "/pos/td/tdo/wrkr/tdoK0203.ajax", "auth": "Bearer", "does": "단건", "params": ["uid", "delFl"]},
      {"key": "tdoK0204", "path": "/pos/td/tdo/wrkr/tdoK0204.ajax", "auth": "Bearer", "does": "메인 대시보드 집계", "params": ["today"]},
      {"key": "tdoK0206", "path": "/pos/td/tdo/wrkr/tdoK0206.ajax", "auth": "Bearer", "does": "등록(UID·정렬키 서버생성)", "params": ["title", "type", "stat", "…노드 칸", "pageUid", "parentUid", "afterUid", "ordKey", "wsUid"]},
      {"key": "tdoK0207", "path": "/pos/td/tdo/wrkr/tdoK0207.ajax", "auth": "Bearer", "does": "이동 — before·after·child·page", "params": ["uid", "mode", "targetUid", "pageUid"]},
      {"key": "tdoK0208", "path": "/pos/td/tdo/wrkr/tdoK0208.ajax", "auth": "Bearer", "does": "수정 · clear=칸,칸 으로 비우기", "params": ["uid", "…바꿀 칸", "clear"]},
      {"key": "tdoK0209", "path": "/pos/td/tdo/wrkr/tdoK0209.ajax", "auth": "Bearer", "does": "삭제(하위까지 휴지통)", "params": ["uid"]},
      {"key": "tdoK0210", "path": "/pos/td/tdo/wrkr/tdoK0210.ajax", "auth": "Bearer", "does": "휴지통 복구(같은 UID, 함께 지운 하위까지)", "params": ["uid"]},
      {"key": "tdoK0211", "path": "/pos/td/tdo/wrkr/tdoK0211.ajax", "auth": "Bearer", "does": "완료 토글", "params": ["uid", "done"]},
      {"key": "tdoK0221", "path": "/pos/td/tdo/wrkr/tdoK0221.ajax", "auth": "Bearer", "does": "루틴 날짜별 체크 조회(기간·항목, 앱·웹과 같은 원장)", "params": ["from", "to", "itemUid", "includeDel"]},
      {"key": "tdoK0222", "path": "/pos/td/tdo/wrkr/tdoK0222.ajax", "auth": "Bearer", "does": "루틴 날짜별 체크하기 — done(완료)·skip(건너뜀)·none(해제)", "params": ["itemUid", "chkDt", "stat"]},
      {"key": "tdoK0231", "path": "/pos/td/tdo/wrkr/tdoK0231.ajax", "auth": "Bearer", "does": "만다라트 — 준비 중(NOT_SUPPORTED + 건의 안내)"}
    ], "note": "shrId 를 붙이면(0202·0203·0206~0211) 내 노트가 아니라 공유받은 페이지(회사 K04)를 다룬다 — 범위 밖은 NOT_FOUND·OUT_OF_SCOPE, 보기(R) 공유로 쓰기는 FORBIDDEN"},
    {"name": "워크스페이스(K05) — 노트 전체를 나누는 칸 · 처음엔 기본 칸 하나 · 무료 기본 포함 2개(3개부터 TreeToDo Plus)", "apis": [
      {"key": "tdoK0501", "path": "/pos/td/tdo/wrkr/tdoK0501.ajax", "auth": "Bearer", "does": "목록(처음 부르면 기본 칸 생성) · count·freeMax·plus·canCreate"},
      {"key": "tdoK0502", "path": "/pos/td/tdo/wrkr/tdoK0502.ajax", "auth": "Bearer", "does": "만들기 — 상한 넘으면 PLUS_REQUIRED(사람이 앱에서 구독)", "params": ["nm", "icon", "wsUid", "attr"]},
      {"key": "tdoK0503", "path": "/pos/td/tdo/wrkr/tdoK0503.ajax", "auth": "Bearer", "does": "이름·아이콘·부가 설정(보낸 칸만 · 기본 칸은 DEFAULT)", "params": ["wsUid", "nm", "icon", "attr"]},
      {"key": "tdoK0504", "path": "/pos/td/tdo/wrkr/tdoK0504.ajax", "auth": "Bearer", "does": "순서", "params": ["order=wsUid,wsUid,…"]},
      {"key": "tdoK0505", "path": "/pos/td/tdo/wrkr/tdoK0505.ajax", "auth": "Bearer", "does": "지우기 — 안의 노트는 기본 칸 휴지통으로 · 기본 칸은 DEFAULT_WS", "params": ["wsUid"]},
      {"key": "tdoK0506", "path": "/pos/td/tdo/wrkr/tdoK0506.ajax", "auth": "Bearer", "does": "페이지(와 하위 전부)를 다른 칸으로 — 대상 칸 최상위 맨 끝", "params": ["uid", "toWsUid(DEFAULT=기본 칸)"]}
    ], "note": "노드의 칸은 wsUid(빈 값 = 기본 칸). tdoK0202 에 wsUid 를 주면 그 칸만(DEFAULT = 기본), tdoK0206 최상위 등록도 wsUid 를 받는다 · 하위는 부모 칸을 따른다."},
    {"name": "회사(K04) — 이용자가 회사를 만들고(최고관리자) 가입 승인·관리자·이양 · 도메인 이메일 인증 · 권한 양도 요청 · 같은 회사 구성원끼리 페이지 공유", "apis": [
      {"key": "tdoK0421", "path": "/pos/td/tdo/wrkr/tdoK0421.ajax", "auth": "Bearer", "does": "내 회사 — 역할(SUPER·ADMIN·MEMBER)·상태(REQ·ACTIVE·REJ)·인증 여부"},
      {"key": "tdoK0422", "path": "/pos/td/tdo/wrkr/tdoK0422.ajax", "auth": "Bearer", "does": "회사 만들기 — 나는 최고관리자", "params": ["corpCd", "corpNm", "domain?", "desc?"]},
      {"key": "tdoK0423", "path": "/pos/td/tdo/wrkr/tdoK0423.ajax", "auth": "Bearer", "does": "회사 찾기 — 코드·도메인 정확히 또는 이름 일부", "params": ["q"]},
      {"key": "tdoK0424", "path": "/pos/td/tdo/wrkr/tdoK0424.ajax", "auth": "Bearer", "does": "가입 신청 — 관리자가 승인하면 구성원", "params": ["corpId|corpCd", "msg?"]},
      {"key": "tdoK0425", "path": "/pos/td/tdo/wrkr/tdoK0425.ajax", "auth": "Bearer", "does": "떠나기·신청 취소 — 내 공유 해제(최고관리자는 이양 먼저)", "params": ["corpId"]},
      {"key": "tdoK0426", "path": "/pos/td/tdo/wrkr/tdoK0426.ajax", "auth": "Bearer", "does": "회사 보기 — 구성원이면 구성원 목록(관리자면 가입 신청 포함)·이 회사에 한 내 공유", "params": ["corpId"]},
      {"key": "tdoK0427", "path": "/pos/td/tdo/wrkr/tdoK0427.ajax", "auth": "Bearer", "does": "가입 승인·거절(관리자)", "params": ["corpId", "mbrId", "act=APRV|REJ"]},
      {"key": "tdoK0428", "path": "/pos/td/tdo/wrkr/tdoK0428.ajax", "auth": "Bearer", "does": "관리자 지정·해제(최고관리자) · 내보내기(관리자)", "params": ["corpId", "mbrId", "act=ADMIN|MEMBER|OUT"]},
      {"key": "tdoK0429", "path": "/pos/td/tdo/wrkr/tdoK0429.ajax", "auth": "Bearer", "does": "최고관리자 이양 — 나는 관리자가 된다", "params": ["corpId", "mbrId"]},
      {"key": "tdoK0430", "path": "/pos/td/tdo/wrkr/tdoK0430.ajax", "auth": "Bearer", "does": "회사 정보 고치기 · 닫기(최고관리자)", "params": ["corpId", "act=EDIT|CLOSE", "corpNm?", "desc?", "domain?"]},
      {"key": "tdoK0431", "path": "/pos/td/tdo/wrkr/tdoK0431.ajax", "auth": "Bearer", "does": "회사 이메일 인증번호 받기 — 사람의 메일함에서 번호를 받아야 한다", "params": ["corpId", "email"]},
      {"key": "tdoK0432", "path": "/pos/td/tdo/wrkr/tdoK0432.ajax", "auth": "Bearer", "does": "인증번호 확인 — 최고관리자가 인증하면 인증된 회사", "params": ["corpId", "email", "code"]},
      {"key": "tdoK0433", "path": "/pos/td/tdo/wrkr/tdoK0433.ajax", "auth": "Bearer", "does": "권한 양도 요청 — 회사 도메인 이메일을 인증한 사람만 · TreeToDo 관리자가 확인", "params": ["corpId", "reason"]},
      {"key": "tdoK0434", "path": "/pos/td/tdo/wrkr/tdoK0434.ajax", "auth": "Bearer", "does": "내 양도 요청 — 목록 · 취소", "params": ["act=LIST|CANCEL", "claimId?"]},
      {"key": "tdoK0401", "path": "/pos/td/tdo/wrkr/tdoK0401.ajax", "auth": "Bearer", "does": "받은 공유 — 페이지마다 한 줄(넓은 권한) · shrId·rootUid·perm·tgtTp(USER·ORG) → 노트 API 에 shrId"},
      {"key": "tdoK0414", "path": "/pos/td/tdo/wrkr/tdoK0414.ajax", "auth": "Bearer", "does": "내 공유 줄 목록", "params": ["stat?", "corpId?", "pageUid?", "tgtTp?"]},
      {"key": "tdoK0415", "path": "/pos/td/tdo/wrkr/tdoK0415.ajax", "auth": "Bearer", "does": "공유 줄 걸기 — 개인(이메일)·조직·공개(보기만, pubUrl) × R|W · 어느 페이지든", "params": ["pageUid", "tgtTp=USER|ORG|PUBLIC", "email(USER)", "corpId(ORG)", "perm=R|W"]},
      {"key": "tdoK0416", "path": "/pos/td/tdo/wrkr/tdoK0416.ajax", "auth": "Bearer", "does": "공유 줄 해제 — 공개면 링크도 즉시 끊김", "params": ["shrId"]},
      {"key": "tdoK0417", "path": "/pos/td/tdo/wrkr/tdoK0417.ajax", "auth": "Bearer", "does": "페이지 공유 상태 — mode(NONE_SET·OWN·INHERIT)·private·grants", "params": ["pageUid"]},
      {"key": "tdoK0418", "path": "/pos/td/tdo/wrkr/tdoK0418.ajax", "auth": "Bearer", "does": "페이지 모드 — 위 설정 따르기 · 비공개", "params": ["pageUid", "mode=INHERIT|PRIVATE"]},
      {"key": "tdoK0419", "path": "/pos/td/tdo/wrkr/tdoK0419.ajax", "auth": "Bearer", "does": "공개 링크 새로(옛 링크 끊김)", "params": ["shrId"]},
      {"key": "tdoK0402", "path": "/pos/td/tdo/wrkr/tdoK0402.ajax", "auth": "none", "does": "공개 읽기(로그인 없음 · 읽기만)", "params": ["t"]}
    ]},
    {"name": "문의·건의(K03) — 1:1 문의함", "apis": [
      {"key": "tdoK0301", "path": "/pos/td/tdo/wrkr/tdoK0301.ajax", "auth": "Bearer", "does": "문의·건의·오류신고 등록 → 접수번호(TDO-n) · 운영자·계정 주인에게 메일", "params": ["kind=inquiry|suggest|bug", "title", "content", "appVer", "os"]},
      {"key": "tdoK0302", "path": "/pos/td/tdo/wrkr/tdoK0302.ajax", "auth": "Bearer", "does": "내 문의 목록", "params": ["stat"]},
      {"key": "tdoK0303", "path": "/pos/td/tdo/wrkr/tdoK0303.ajax", "auth": "Bearer", "does": "문의 상세(답글 포함)", "params": ["inqNo"]},
      {"key": "tdoK0304", "path": "/pos/td/tdo/wrkr/tdoK0304.ajax", "auth": "Bearer", "does": "추가 글(재접수)", "params": ["inqNo", "content"]},
      {"key": "tdoK0305", "path": "/pos/td/tdo/wrkr/tdoK0305.ajax", "auth": "Bearer", "does": "종료", "params": ["inqNo"]}
    ]},
    {"name": "정보(K09) — 로그인 없이", "apis": [
      {"key": "tdoK0901", "path": "/pos/td/tdo/wrkr/tdoK0901.ajax", "auth": "none", "does": "이 목록(API 안내)"},
      {"key": "tdoK0902", "path": "/pos/td/tdo/wrkr/tdoK0902.ajax", "auth": "none", "does": "이용약관·개인정보처리방침 — 판·시행일·주소·본문", "params": ["doc=terms|privacy", "lang=ko|en", "body=Y"]},
      {"key": "tdoK0903", "path": "/pos/td/tdo/wrkr/tdoK0903.ajax", "auth": "none", "does": "버전 — 앱 최신·기록 · PC 설치판 · API 판", "params": ["history=Y"]},
      {"key": "tdoK0904", "path": "/pos/td/tdo/wrkr/tdoK0904.ajax", "auth": "none", "does": "서비스 정보 — 회사·연락처·주소·공지"}
    ]}
  ],
  "humanActionCodes": [
    {"code": "TERMS_CONSENT", "when": "가입 ① 약관 동의 전", "human": "약관·개인정보처리방침을 읽고 동의"},
    {"code": "EMAIL_CODE", "when": "가입 ① 뒤 · ② 번호가 틀렸을 때", "human": "메일함의 6자리 번호를 알려 줌(10분)"},
    {"code": "PASSWORD_RESET_CODE", "when": "비밀번호 재설정 요청 뒤", "human": "메일함의 번호 + 새 비밀번호"},
    {"code": "CAPTCHA", "when": "재설정 번호를 여러 번 틀림", "human": "브라우저에서 웹을 열어 직접 마침(API 로 대신 못 함)"},
    {"code": "ACCOUNT_LOCKED", "when": "로그인 5회 실패", "human": "기다리거나 비밀번호 재설정(메일 번호)"},
    {"code": "TERMS_RECONSENT", "when": "약관 판이 바뀜", "human": "바뀐 문서를 읽고 다시 동의 → tdoK0104"}
  ],
  "errorCodes": [
    {"code": "UNAUTHORIZED", "reason": "TOKEN_EXPIRED", "next": "/pos/front/td/front/auth/refresh"},
    {"code": "UNAUTHORIZED", "reason": "TOKEN_REVOKED", "next": "다시 로그인 — 로그아웃·비밀번호 변경·탈퇴 뒤"},
    {"code": "UNAUTHORIZED", "reason": "TOKEN_INVALID", "next": "다시 로그인 — 위조·refresh 를 access 자리에"},
    {"code": "UNAUTHORIZED", "reason": "TOKEN_MISSING", "next": "로그인해서 Bearer 헤더"},
    {"code": "BAD_PARAM", "next": "resultMessage 를 보고 값·위치를 고침"},
    {"code": "NOT_FOUND", "next": "uid 확인 — 남의 노트도 '없음'으로 보인다(존재를 드러내지 않음)"},
    {"code": "STALE_UPDATE", "next": "다른 기기에 더 최신 수정 — 다시 읽고 고침"},
    {"code": "EMAIL_EXISTS", "next": "로그인 또는 비밀번호 재설정"},
    {"code": "CODE_INVALID", "next": "humanAction EMAIL_CODE — 번호를 다시 물어봄"},
    {"code": "CODE_EXPIRED", "next": "가입 ① 부터 다시"},
    {"code": "DAILY_LIMIT", "next": "내일 다시(가입 인증번호·문의 하루 상한)"},
    {"code": "NOT_SUPPORTED", "next": "건의 API tdoK0301 (next.suggest 에 제목 제안)"},
    {"code": "OUT_OF_SCOPE", "next": "공유받은 페이지 밖으로 놓으려 함 · 공유 루트 자체를 옮기거나 지우려 함 — 범위 안 자리로"},
    {"code": "FORBIDDEN", "next": "보기(R) 공유로 쓰기 · 회사 역할로 못 하는 일 — 노트 주인·관리자에게 요청"},
    {"code": "NOT_MEMBER", "next": "회사 구성원이 아님 — tdoK0424 가입 신청 → 관리자 승인"},
    {"code": "NOT_VERIFIED", "next": "회사 이메일 인증 먼저 — tdoK0431 → tdoK0432"},
    {"code": "DOMAIN_TAKEN", "next": "같은 도메인의 인증 회사가 이미 있음 — 그 회사에 가입 신청 또는 양도 요청"},
    {"code": "CONFLICT", "next": "회사 코드 중복 · 이미 처리됨 · 열린 요청 있음 — resultMessage"},
    {"code": "LIMIT", "next": "최고관리자 회사 5개 상한"},
    {"code": "MAIL_FAIL", "next": "메일 발송 실패 — 잠시 뒤 다시"}
  ]
}
