= Api AhaWiki 페이지를 외부 사용자, 자동화 도구, AI 에이전트가 API Key로 읽고 저장하기 위한 API입니다. AhaWiki API는 세션 쿠키 대신 `Authorization: Bearer ` 헤더를 사용합니다. AhaWiki API 요청에는 CSRF token과 reCAPTCHA가 필요하지 않습니다. 읽기/쓰기 endpoint path는 `/api/v1/` prefix를 사용합니다. == 지원 기능 * API Key 인증 사용자로 페이지 읽기 * API Key 인증 사용자로 페이지 저장 * API Key 인증 사용자로 페이지 목록/메타 조회 * API Key 인증 사용자로 최근 변경 조회 * API Key 인증 사용자로 페이지 이름변경/삭제 * 저장된 revision에 `viaApi = true`와 저장에 사용한 API Key(`userApiKey`) 기록 * `/api/v1/changes` 응답과 History·RecentChanges 화면에 `viaApi`와 key 이름(`userApiKeyName`) 표시 — 아래 '''변경 이력 표시와 필터''' == 제한사항 * API로 저장·이름변경·삭제하면 텔레그램 알림이 가지 않습니다. API 저장은 그 페이지를 열어 둔 브라우저에 실시간 갱신 알림도 보내지 않습니다. == 인증 AhaWiki API 호출에는 아래 헤더를 넣습니다. [[[#!Vim text Authorization: Bearer ahawiki_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ]]] API Key는 생성 화면에서 한 번만 plain text로 표시됩니다. 이후 목록 조회에서는 `keyPrefix`만 볼 수 있고 원본 key는 다시 조회할 수 없습니다. API Key는 Account Settings의 API Keys 섹션에서 생성하고 폐기할 수 있습니다. 키가 유출되면 Account Settings에서 폐기해야 합니다. 이 문서는 `Authorization: Bearer `로 호출하는 API만 다룹니다. 브라우저 세션과 CSRF token이 필요한 Account/Admin API는 공개 API로 다루지 않습니다. == 페이지 읽기 [[[#!Vim sh curl https://ahawiki.net/api/v1/page/FrontPage \ -H "Authorization: Bearer " ]]] 응답: [[[#!Vim json { "name": "FrontPage", "revision": 12, "content": "= FrontPage\n...", "dateTime": "2026-06-24T10:00:00", "isMinorEdit": false, "viaApi": false, "userApiKeyName": null, "contentHash": "sha256:..." } ]]] 권한은 API Key 소유자의 기존 `WikiPermission`을 따릅니다. 읽기 권한이 없으면 `403 Forbidden`을 반환합니다. == 페이지 목록/메타 조회 [[[#!Vim sh curl "https://ahawiki.net/api/v1/pages?prefix=ToDo&limit=100" \ -H "Authorization: Bearer " ]]] 응답: [[[#!Vim json { "pages": [ { "name": "ToDo", "revision": 81, "dateTime": "2026-06-24T22:00:00", "isMinorEdit": false, "viaApi": true, "userApiKeyName": "AhaWikiDoc", "size": 3223, "contentHash": "sha256:..." } ] } ]]] `contentHash`는 전체 본문을 내려받지 않고 local/remote 차이를 빠르게 비교하기 위한 SHA-256 hash입니다. 응답은 API Key 소유자가 읽을 수 있는 페이지로 제한됩니다. == Batch 페이지 메타 조회 [[[#!Vim sh curl -X POST https://ahawiki.net/api/v1/pages/metadata \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"names":["Api","Dev Api","MissingPage"]}' ]]] 응답: [[[#!Vim json { "pages": [ { "name": "Api", "revision": 1, "dateTime": "2026-06-24T22:20:29", "isMinorEdit": false, "viaApi": true, "userApiKeyName": "AhaWikiDoc", "size": 5548, "contentHash": "sha256:..." } ], "missing": ["MissingPage"], "forbidden": [] } ]]] == 페이지 저장 [[[#!Vim sh curl -X POST https://ahawiki.net/api/v1/page/FrontPage \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "revision": 12, "text": "= FrontPage\nupdated content", "comment": "Update via API", "minorEdit": false }' ]]] 요청 필드: * `revision` — 저장 기준 revision. 최신 revision과 다르면 저장하지 않습니다. * `text` — 저장할 전체 페이지 본문. * `comment` — 저장 comment. 생략하면 빈 문자열입니다. * `minorEdit` — minor edit 여부. 생략하면 `false`입니다. 응답: [[[#!Vim json { "name": "FrontPage", "revision": 13, "dateTime": "2026-06-24T10:01:00" } ]]] 저장 성공 시 새 revision은 `viaApi = true`로 기록됩니다. 웹 편집과 달리 알림을 보내지 않습니다(위 '''제한사항'''). == 페이지 이름변경 [[[#!Vim sh curl -X POST https://ahawiki.net/api/v1/rename \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "name": "OldPage", "newName": "NewPage", "revision": 3, "comment": "Rename via API" }' ]]] 요청 revision이 최신 revision과 다르면 `409 Conflict`를 반환합니다. 대상 페이지가 이미 있으면 `409 Conflict`를 반환합니다. 성공하면 기존 페이지 이름을 `newName`으로 바꾸고, 기존 이름에는 redirect page를 생성합니다. redirect revision은 `viaApi = true`로 기록됩니다. == 페이지 삭제 [[[#!Vim sh curl -X DELETE https://ahawiki.net/api/v1/page/OldPage \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "revision": 3, "confirm": true }' ]]] 삭제는 되돌리기 어려운 작업이므로 `confirm: true`가 필요합니다. 요청 revision이 최신 revision과 다르면 `409 Conflict`를 반환합니다. 삭제 성공 시 페이지에 연결된 첨부파일도 웹 삭제와 같은 정책으로 삭제 처리합니다. == 최근 변경 조회 [[[#!Vim sh curl "https://ahawiki.net/api/v1/changes?prefix=ToDo&since=2026-06-24T00:00:00&limit=100" \ -H "Authorization: Bearer " ]]] query parameter: * `name` — 특정 page name만 조회. `afterRevision`을 사용할 때 필요합니다. * `prefix` — page name prefix 필터 * `since` — ISO-8601 local date-time 이후 변경만 조회 * `afterRevision` — `name`으로 지정한 단일 페이지에서 해당 revision 초과 변경만 조회 * `includeMinorEdit` — `0`이면 minor edit 제외, 기본값은 `1` * `includeViaApi` — `0`이면 API Key 저장 제외, 기본값은 `1` * `limit` — 최대 행 수, 기본값은 `100` `revision`은 페이지별 번호이므로 `afterRevision`은 전체 sync cursor로 사용할 수 없습니다. 전체 동기화는 `since`와 페이지별 metadata 비교를 함께 사용합니다. 응답: [[[#!Vim json { "changes": [ { "name": "Api", "revision": 2, "dateTime": "2026-06-24T22:00:00", "comment": "AhaWikiDoc sync Api", "isMinorEdit": false, "viaApi": true, "userApiKeyName": "AhaWikiDoc" } ] } ]]] == AhaWikiDoc sync 권장 방식 문서 동기화 도구는 서버에 별도 sync state를 저장하지 않고 local manifest를 갱신하는 방식을 권장합니다. 1. `GET /api/v1/pages`로 원격 페이지의 `revision`, `dateTime`, `contentHash`를 조회합니다. 2. local manifest의 마지막 동기화 시각 또는 page별 revision과 비교합니다. 3. 변경 후보만 `GET /api/v1/page/*name`으로 본문을 내려받아 local file과 비교합니다. 4. local에서 검토/커밋된 문서가 원격보다 최신이면 `POST /api/v1/page/*name`으로 저장합니다. 5. 원격이 최신이면 local file을 갱신합니다. 6. 양쪽이 모두 바뀐 경우 자동 덮어쓰기를 하지 말고 병합하거나 사용자에게 확인합니다. 7. 저장/가져오기 완료 후 local manifest에 `lastSyncedAt`, page별 `revision`, `dateTime`, `contentHash`를 기록합니다. '''이 저장소의 참조 구현은 2·3·5·7번을 하지 않습니다.''' `scripts/sync.ahawiki.net.mjs`는 별도 manifest 대신 git 히스토리를 마지막 동기화 기록으로 쓰고, 본문을 내려받아 비교하지 않고 목록의 `contentHash`만 비교합니다 — 원격 본문이 그 파일의 최근 커밋 40개 중 '''예전 버전'''과 같으면 로컬이 앞선 것으로 보고 `--apply`일 때 저장하고, 그렇지 않으면 브라우저에서 편집된 것으로 보고 diverged로 알리기만 합니다. 로컬 파일은 바꾸지 않습니다 — diverged는 사람이 병합해 커밋하고, 로컬에 없는 페이지는 `download:ahawiki.net`으로 받습니다. 문서가 이미 git 으로 관리되므로 같은 사실을 manifest 에 한 번 더 적으면 그 둘이 갈라지기 때문입니다. manifest 방식은 문서가 git 밖에 있는 도구에 유효합니다. `docs/ahawiki.net/manifest.json`은 sync state가 아니라 `download:ahawiki.net`이 남기는 내려받기 기록입니다. == 변경 이력 표시와 필터 API Key로 저장한 revision은 `viaApi = true`로 기록되고, 저장에 사용한 key는 `userApiKey`로 기록됩니다. 응답의 `userApiKeyName`은 저장에 쓴 key의 현재 이름입니다. 웹에서 저장한 revision은 `null`입니다. key를 폐기해도 이름은 그대로 나옵니다 — 폐기는 key를 못 쓰게 할 뿐 기록을 지우지 않습니다. * History 화면에는 minor edit, Via API 이모지 컬럼과 `Show ViaApi` 필터가 있고, Via API 편집에는 key 이름이 함께 표시됩니다. * `RecentChanges` 매크로에는 `Include via API edits` 토글과 minor edit, Via API 이모지 컬럼이 있습니다. 토글은 처음에 켜져 있습니다([MacroRecentChanges]). * `/api/change`는 `includeViaApi` 파라미터를 받습니다. 기본값은 `0`입니다. * Admin Recent Changes 화면에는 viaApi 포함/제외 토글과 minor edit, Via API 이모지 컬럼이 있습니다. * Admin Dashboard의 Recent Changes 요약 표도 minor edit와 API 편집을 함께 불러와, 같은 이모지 컬럼으로 표시합니다. 2026-09-13 전에는 둘을 빼고 불러와서 두 컬럼이 늘 비어 있었습니다. == 저장 Flow 자동화 도구는 아래 순서로 저장합니다. 1. `GET /api/v1/page/*name`으로 현재 본문과 `revision`을 읽습니다. 2. 본문을 수정합니다. 3. `POST /api/v1/page/*name`에 읽었던 revision과 수정된 전체 text를 함께 보냅니다. 4. `409 Conflict`가 오면 최신 본문을 다시 읽고 변경 내용을 병합한 뒤 재시도합니다. == AI 자동화 프롬프트 예시 아래 프롬프트를 AI 코딩 에이전트나 자동화 도구에 전달하면 AhaWiki API로 페이지를 읽고 수정하게 할 수 있습니다. ``, ``, ``는 실제 값으로 바꿉니다. [[[#!Vim text 너는 AhaWiki 문서를 수정하는 자동화 에이전트다. 인증: - 모든 AhaWiki API 요청에 `Authorization: Bearer ` 헤더를 넣어라. - CSRF token, session cookie, reCAPTCHA는 AhaWiki API에 필요 없다. - API Key를 로그, 문서 본문, 저장 comment에 노출하지 마라. 대상: - Base URL: https://ahawiki.net - Page: - 작업: 읽기: 1. `GET https://ahawiki.net/api/v1/page/`를 호출한다. 2. 응답의 `revision`과 `content`를 저장한다. 3. `403`이면 권한 없음으로 중단하고, `404`이면 페이지 없음으로 보고한다. 수정: 1. 기존 `content`를 기준으로 필요한 부분만 바꾼 전체 본문을 만든다. 2. 관련 없는 내용, 포맷, 줄바꿈은 가능한 유지한다. 3. 저장 comment는 사람이 이해할 수 있게 짧게 쓴다. 4. 사소한 포맷/오타 수정이면 `minorEdit: true`, 의미 있는 내용 변경이면 `minorEdit: false`로 보낸다. 저장: `POST https://ahawiki.net/api/v1/page/`에 아래 JSON을 보낸다. { "revision": <읽은 revision>, "text": "<수정된 전체 본문>", "comment": "<저장 comment>", "minorEdit": false } 충돌 처리: - `409 Conflict`가 오면 최신 페이지를 다시 읽고 내 변경사항을 최신 본문에 병합한 뒤 한 번 더 저장한다. - 병합이 애매하면 저장하지 말고 충돌 내용을 보고한다. 완료 보고: - 저장 성공 시 새 revision을 보고한다. - 저장하지 못하면 HTTP status와 원인을 보고한다. ]]] == 에러 코드 [[[#!Table tsv 1 코드 의미 `400 Bad Request` JSON body 없음, `revision`·`text` 누락, 본문 변경 없음, 삭제에 `confirm: true` 없음, 이름변경에 `name`·`newName` 없음, `since` 형식 오류, `name` 없이 `afterRevision` 사용 `401 Unauthorized` API Key 없음 또는 유효하지 않음 `403 Forbidden` API Key 소유자에게 그 작업의 권한 없음 `404 Not Found` 읽기·이름변경·삭제 대상 페이지가 없음 `409 Conflict` 요청 revision이 최신 revision과 다름(응답에 `latestRevision` 포함), 또는 이름변경 대상 이름이 이미 있음 `500 Internal Server Error` 삭제 중 첨부파일 삭제 실패 ]]] == 관련 * ["Dev Api"]