콘텐츠로 건너뛰기

개발자 문서

최종 업데이트 2026년 9월 26일.

Changeloop가 여러분을 위해 게시하는 모든 것은 HTTPS를 통한 단순한 JSON입니다. 설치할 SDK도, 순환시킬 API 키도, 로그인 단계도 필요 없습니다. 아래 두 피드는 여러분의 피드 ID로 키가 지정된 익명의 공개 읽기입니다. 이 페이지의 모든 예시에서 YOUR_PUBLIC_ID를 여러분의 것으로 바꾸세요.

시작하기 전에 알아두면 좋은 한 가지: 여러분의 공개 피드 ID는 앱 자체에 있습니다. 로그인하여 설정을 열면, 기본적으로 표시되는 '공개 피드' 섹션에 바로 있으며, changelog.json 및 roadmap.json으로의 바로 사용 가능한 링크, 호스팅된 피드 페이지 링크, 아래의 위젯 스니펫과 함께 각각 자체 복사 버튼이 있습니다.

시작하기

가입부터 여러분의 사이트에 changelog를 올리기까지 다섯 단계면 됩니다. 앱의 'Get started' 페이지가 단계별로 안내하고, 끝난 단계는 체크해 줍니다.

  1. 소스를 연결합니다. GitHub 저장소, GitLab 프로젝트 또는 Bitbucket 저장소 중 하나입니다.
  2. 항목을 작성할 언어를 고릅니다.
  3. 원하면 태그를 만들어 독자가 제품 영역별로 필터링할 수 있게 합니다.
  4. 첫 번째 항목을 게시합니다. 병합된 변경 사항은 초안으로 검토함에 들어옵니다. 하나를 승인하거나 해당 저장소의 자동 게시를 켜세요.
  5. 사이트에 올립니다. 호스팅된 페이지로 링크하거나, 위젯을 붙여 넣거나, 여러분의 페이지에서 JSON 피드를 표시합니다.

약 10줄의 React로 만드는 changelog

이것을 컴포넌트에 붙여넣으면 작동하는 changelog가 완성됩니다. 더 추가할 것이 없습니다.

import { useEffect, useState } from 'react';

const FEED = 'https://api.changeloop.dev/v1/public/YOUR_PUBLIC_ID/changelog.json';

export function Changelog() {
  const [entries, setEntries] = useState([]);
  useEffect(() => {
    fetch(FEED).then((r) => r.json()).then((feed) => setEntries(feed.data));
  }, []);
  return <ul>{entries.map((e) => <li key={e.id}><b>{e.title}</b><p>{e.mdContent}</p></li>)}</ul>;
}

mdContent는 당사가 텍스트로 작성한 markdown입니다. 형식이 지정된 출력을 렌더링하고 싶다면 대신 htmlContent를 사용하세요. 이는 허용된 태그와 속성의 고정 목록에서 당사 자체 새니타이저에 의해 서버 측에서 구축되며, 이 응답들 중 마크업으로 삽입하도록 의도된 유일한 값입니다. 나머지는 모두 텍스트이며, 공개 저장소에서 작성된 항목은 그곳에서 풀 리퀘스트를 열 수 있는 누구에게나 영향을 받을 수 있으므로 그에 맞게 처리하세요.

changelog 피드

GET/v1/public/YOUR_PUBLIC_ID/changelog.json

게시된 항목을 최신 순으로 제공하며, 동일한 타임스탬프의 경우 최신 ID로 동점을 결정합니다.

쿼리 매개변수

  • repos는 acme/web,acme/api처럼 쉼표로 구분된 전체 저장소 이름 목록을 받습니다. 해당 저장소의 항목만 반환됩니다. 비워두면 모두 가져옵니다.
  • limit은 페이지당 원하는 항목 수입니다. 기본값은 20이며, 50을 초과하는 값은 50으로 제한되고, 양수로 해석할 수 없는 값은 실패하는 대신 20으로 돌아갑니다.
  • cursor는 불투명합니다. 이전 응답의 nextCursor 값을 그대로 반환하세요. 디코딩할 수 없는 커서는 커서가 없는 것으로 처리되어 오류 대신 첫 페이지를 다시 받게 됩니다.

응답

{
  "data": [
    {
      "id": "66b0c1f2e4a9d1c3b5a70011",
      "title": "Saved views on the inbox",
      "mdContent": "You can now pin a filter and come back to it.",
      "htmlContent": "<p>You can now pin a filter and come back to it.</p>",
      "repoFullName": "acme/web",
      "category": "feature",
      "tags": ["Inbox"],
      "learnMoreUrl": "https://acme.example/docs/saved-views",
      "publishedAt": "2026-08-06T09:12:44.000Z"
    }
  ],
  "nextCursor": null,
  "tagColors": { "Inbox": "#4f46e5" }
}

모든 항목은 동일한 9개의 키를 가집니다: id, title, mdContent, htmlContent, repoFullName, category, tags, learnMoreUrl, publishedAt. category는 feature, fix, internal 중 하나이며 작성자가 설정하지 않았으면 null입니다. publishedAt은 ISO 8601 문자열이며, htmlContent는 작성기를 거치지 않은 항목에서는 빈 문자열입니다. tags는 여러분 자신의 제품 영역 이름 배열이며 할당된 것이 없으면 비어 있습니다. learnMoreUrl은 검토자가 추가하지 않는 한 null이며, 각 태그를 그릴 색상은 항목이 아니라 응답의 tagColors 맵에서 가져오므로 어휘에서 제거한 태그는 단순히 색상 없이 렌더링됩니다. 끝에 도달하면 nextCursor는 null입니다.

알 수 없는 피드 ID는 {"error":"not_found"}와 함께 404를 반환하며, 형식이 잘못된 것도 마찬가지입니다. 이 둘은 의도적으로 구별할 수 없게 되어 있어, 이 엔드포인트로 어떤 ID가 존재하는지 알아낼 수 없습니다.

roadmap 피드

GET/v1/public/YOUR_PUBLIC_ID/roadmap.json

여러분의 팀이 이미 수동으로 관리하는 것과 동일한 세 개의 열.

{
  "columns": [
    { "column": "planned", "items": [], "hasMore": false },
    {
      "column": "building",
      "items": [
        {
          "id": "66b0c1f2e4a9d1c3b5a70042",
          "column": "building",
          "publicTitle": "Slack notifications",
          "publicDescription": "Post each published entry to a channel you pick.",
          "publishedAt": "2026-08-05T16:20:01.000Z"
        }
      ],
      "hasMore": false
    },
    { "column": "shipped", "items": [], "hasMore": false }
  ]
}

columns는 열 이름으로 키가 지정된 객체가 아니라 배열이며, 그 순서는 계약의 일부입니다: planned, 그다음 building, 그다음 shipped. 세 개 모두 비어 있는 것을 포함해 항상 존재하므로 '그런 열이 없음'과 '아직 아무것도 없음'을 구별할 필요가 전혀 없습니다. 받은 순서대로 렌더링하면 당사가 구축하는 다른 모든 표면과 일치합니다.

항목은 정확히 다섯 개의 키를 가집니다: id, column, publicTitle, publicDescription, publishedAt. publicDescription은 항상 문자열이며 비어 있을 수는 있지만 null이 되는 일은 없습니다. 항목의 출처가 된 issue에 대한 정보는 저장소도 issue 번호도 여기서 전혀 공개되지 않으며, 이는 의도적인 것이지 나중에 채울 누락이 아닙니다.

이 엔드포인트는 쿼리 매개변수를 전혀 받지 않습니다. roadmap은 무한히 성장하는 로그가 아니라 사람이 큐레이션하는 작은 보드이기 때문에 커서도, limit도, 저장소 필터도 없습니다. 각 열은 최대 50개의 항목을 반환하고 그보다 많으면 hasMore를 설정합니다. hasMore는 정보 제공용입니다: 이를 따를 커서가 없으므로 이를 중심으로 페이지네이션을 구축하지 마세요.

publicTitle과 publicDescription은 issue의 제목과 본문에서 작성된 일반 텍스트이며, 공개 저장소에서는 issue를 열 수 있는 누구에게나 영향을 받을 수 있습니다. HTML 새니타이징 보장이 전혀 없으며 htmlContent의 예외도 아닙니다. 텍스트로 렌더링하세요.

임베드 위젯

아무것도 구축하고 싶지 않다면 이 두 줄을 삽입하세요. 위젯은 shadow root 내에 렌더링되는 커스텀 요소이므로 여러분의 스타일을 상속받지도, 그쪽으로 새어나가지도 않습니다.

<script src="https://api.changeloop.dev/widget.js" defer></script>
<changelogapp-widget
  data-public-id="YOUR_PUBLIC_ID"
  data-api="https://api.changeloop.dev"></changelogapp-widget>

두 속성 모두 필수입니다. data-public-id는 여러분의 피드 ID이고, data-api는 위젯이 데이터를 가져오는 오리진입니다. 둘 중 하나라도 없으면 요소는 콘솔에 오류를 기록하고 아무것도 렌더링하지 않으며, 이는 위젯이 있어야 할 자리에 빈 공간이 보인다면 가장 먼저 확인할 사항입니다.

요소에 data-theme="dark"를 추가하면 다크 모드로 렌더링되며, 페이지에서 런타임에 전환할 수 있습니다. 더 세밀한 스타일링을 위해 위젯은 CSS 사용자 지정 속성(--changelogapp-text, --changelogapp-bg, --changelogapp-accent 등)과 ::part() 이름을 제공하며, 여러분의 스타일시트에서 설정합니다. 앱의 설정, 공개 피드에서 두 테마를 실시간으로 미리 볼 수 있습니다.

data-repos를 추가하면 일부 리포지토리만 표시할 수 있습니다. 예를 들어 여러 제품이 한 계정을 공유할 때 한 제품의 사이트에 그 제품의 changelog만 보여 줄 수 있습니다. 값은 owner/repo 형식의 전체 이름을 쉼표로 구분한 목록입니다. 소유자를 뺀 이름은 아무것에도 일치하지 않으며 오류 없이 빈 피드가 표시됩니다. 최대 10개 리포지토리까지 적용됩니다. 범위를 좁힌 위젯에는 Updates와 Feedback 탭만 표시됩니다. 로드맵에는 리포지토리별 보기가 없기 때문이며, 피드백은 여전히 팀의 피드백 대상이 가리키는 곳에 등록됩니다. 설정의 공개 피드에는 이 속성을 대신 작성해 주는 선택 도구가 있습니다.

다음 순서로 세 개의 탭을 렌더링합니다: Updates, Roadmap, Feedback. 처음 두 개는 위의 피드를 읽습니다. 세 번째는 아래 엔드포인트로 전송하고 각 제출의 ID를 localStorage에 보관하여, 방문자가 돌아와서 보낸 것이 어떻게 되었는지 확인할 수 있습니다.

스크립트는 버전이 지정되어 제공됩니다. /widget.js는 항상 최신 빌드를 제공하고 1시간 동안 캐시되므로, 여러분이 아무것도 건드리지 않아도 릴리스가 방문자에게 도달합니다. /widget-vN.js는 하나의 빌드를 고정합니다: 버전 번호가 한 번 제공되면 그 바이트는 두 번 다시 바뀌지 않으며 1년 동안 캐시됩니다. 변경 사항을 의도적으로 채택하고 싶다면 고정하세요.

페이지당 정확히 하나의 위젯 스크립트를 로드하세요

두 URL은 대안이지 계층이 아닙니다. 둘 다 동일한 커스텀 요소 이름을 등록하며, 브라우저는 문서당 이름을 한 번만 등록할 수 있게 하므로, 먼저 실행된 스크립트가 페이지가 살아있는 동안 승리하고 두 번째는 비활성화됩니다. 따라서 /widget.js와 /widget-v5.js를 모두 포함한 페이지는 브라우저가 먼저 실행한 것을 렌더링하며, 이는 여러분이 제어할 수 있는 것이 아닙니다. 버전을 고정하기 위해 기존 /widget.js 옆에 /widget-v5.js를 추가해도 아무 효과가 없습니다.

이런 일이 발생하면 위젯은 두 빌드를 모두 지목하는 경고를 콘솔에 기록하므로 추측할 필요가 없습니다. 경고 이상의 일은 할 수 없습니다: 두 번째 사본이 실행될 즈음에는 첫 번째가 이미 그 이름을 차지했기 때문입니다. 해결책은 항상 다른 것을 추가하는 대신 스크립트 태그를 교체하는 것이며, 태그 관리자나 부분 템플릿이 대신 삽입하는 경우도 마찬가지입니다. 계속 진행되는 빌드에서 고정된 빌드로 옮기려면 src를 변경하세요.

호스팅된 피드 페이지

https://feed.changeloop.dev/feed/YOUR_PUBLIC_ID

당사는 해당 주소에도 단순한 페이지를 호스팅합니다: 여러분의 changelog와 roadmap 보드가 위와 동일한 두 피드에서 렌더링됩니다. 로그인이나 여러분 측의 설정이 필요 없습니다. 이는 또한 루프가 닫힐 때 사람들을 돌려보내는 곳이기도 합니다: GitHub issue에 남기는 Shipped 댓글은 여기로 링크되며, 위의 제출 조회에서 나온 shippedEntry.link도 마찬가지로, 둘 다 전달된 항목의 자체 #entry-ID 앵커에 도달하는데, 이는 이후 나중 페이지로 이동했더라도 항목을 찾아냅니다.

이것을 통합이 아닌 대체 수단으로 취급하세요. 이것을 당사가 아닌 여러분의 제품처럼 보이도록 여러분 자신의 사이트에 배치하는 방법은 여전히 changelog 피드와 위젯입니다. 이 페이지는 아직 그렇게 하지 않았을 때, 그리고 다른 무엇을 구축했든 관계없이 여기를 가리키는 루프 종료 링크를 위한 것입니다.

자체 도메인

DNS나 인증서를 변경하지 않고 호스팅된 페이지를 여러분의 주소에서 제공할 수 있습니다. 설정, 사용자 지정 도메인에서 독자가 보게 될 공개 주소(예: https://example.com/changelog)를 붙여넣은 뒤, 사이트의 해당 경로를 그곳에 표시된 프록시 대상으로 연결하세요. 규칙 하나로 페이지, 에셋, 데이터, 피드를 모두 처리합니다. 내 도메인 확인은 당사 쪽에서 여러분의 주소를 가져와 프록시가 올바른지, 아니라면 무엇을 바꿔야 하는지 알려 줍니다.

MCP 서버

POSThttps://api.changeloop.dev/mcp

Claude Code, ChatGPT, 또는 Model Context Protocol을 지원하는 다른 에이전트로 작업 중이라면 이를 여러분의 changelog에 직접 연결할 수 있습니다. 그러면 에이전트는 검토를 기다리는 것을 확인하고, 문구를 편집하고, 에디터를 벗어나지 않고 게시할 수 있습니다. 이는 웹 앱과 동일한 검토 관문입니다: 무언가가 승인하기 전까지는 아무것도 공개되지 않습니다.

Claude Code 연결하기

먼저 API 키를 생성한 후(설정, API 키), 헤더에 키를 포함하여 서버를 추가하세요:

claude mcp add --transport http changeloop \
  https://api.changeloop.dev/mcp \
  --header "Authorization: Bearer clapi_YOUR_KEY"

대신 JSON 설정을 읽는 클라이언트의 경우, 동일한 내용은 다음과 같습니다:

{
  "mcpServers": {
    "changeloop": {
      "type": "http",
      "url": "https://api.changeloop.dev/mcp",
      "headers": { "Authorization": "Bearer clapi_YOUR_KEY" }
    }
  }
}

아직 OAuth 흐름은 없습니다. 인증은 헤더의 API 키이며, 위의 두 명령이 하는 일입니다. 설정에서 해당 키를 취소하면 다음 요청에서 에이전트의 연결이 끊깁니다.

에이전트가 할 수 있는 것

일곱 개의 도구가 있으며, 목록은 의도적으로 짧게 유지됩니다. 이 제품이 할 수 있는 다른 모든 것은 동일한 키로 REST API를 통해 접근할 수 있습니다. 에이전트에 노출된 각 도구는 호출하도록 설득될 수 있는 또 하나의 수단입니다.

  • list_pending_entries, list_published_entries, get_entry - 여러분의 항목을 읽습니다. 대기 중인 것은 공개되지 않습니다.
  • update_entry - 항목의 제목이나 markdown 본문을 변경합니다. 피드가 제공하는 HTML은 여러분의 markdown에서 당사의 새니타이저에 의해 다시 렌더링됩니다. 에이전트는 HTML을 제공할 수 없습니다.
  • approve_entry - 게시합니다. 이는 공개적이고 즉각적이며, GitHub에 연결된 모든 피드백에 알립니다. 대기 중인 항목만 승인할 수 있습니다.
  • discard_entry - 항목을 changelog에서 제외합니다. 웹 앱에서 되돌릴 수 있습니다.
  • get_changelog_info - 여러분의 피드 ID와 changelog가 제공되는 주소.

할 수 없는 것

모든 도구는 키가 속한 팀으로 범위가 제한되며, 어떤 도구도 팀을 인자로 받지 않으므로 무언가가 시도해도 다른 팀을 가리킬 방법이 전혀 없습니다. 서버는 브라우저 세션을 받아들이지 않고 키만 받아들입니다: 요청은 의도적으로 자격증명을 첨부해야 합니다. 또한 키는 키를 관리하거나 데이터 내보내기를 다운로드할 수 없으므로, 이 방식으로 연결된 에이전트는 자신을 위한 두 번째 자격증명을 발급하거나 한 번의 호출로 여러분의 데이터를 빼낼 수 없습니다.

API 키

위의 모든 것은 익명이며 자격증명이 필요 없습니다. 인증된 API - 여러분의 설정, 여러분의 검토함 - 은 다른 표면이며 로그인된 브라우저 세션이나 API 키 중 하나를 받아들입니다. 키는 스크립트와 에이전트를 위한 것입니다: 키보드 앞에 사람이 없는 상태로 여러분의 changelog에 도달해야 하는 모든 것.

Authorization: Bearer clapi_YOUR_KEY

앱의 설정 아래 API 키 탭에서 하나를 만드세요. 키는 만든 순간에 한 번만 표시되고 다시는 표시되지 않습니다: 당사는 그 해시만 저장하므로 두 번째로 그것을 보여줄 화면이 어디에도 없습니다. 분실했다면 그것을 취소하고 다른 것을 만드세요.

키가 할 수 있는 것과 할 수 없는 것

키는 로그인과 동일한 접근 권한을 가지며 생성된 단일 팀으로 범위가 제한되고, 두 가지 의도적인 예외가 있습니다. API 키를 관리할 수 없고, 데이터 내보내기를 다운로드할 수 없습니다. 둘 다 실제 로그인이 필요합니다. 그래야 유출된 키가 스스로 대체품을 발급하거나, 그것을 차단하는 데 사용할 키를 취소하거나, 한 번의 요청으로 여러분 팀의 데이터를 빼낼 수 없습니다.

취소

취소는 다음 요청에서 효력이 발생합니다. 취소된 키는 알 수 없는 키와 정확히 똑같이 401을 반환하며, 여전히 유효한 세션을 보유한 브라우저에서도 계속 401을 반환합니다. Authorization 헤더를 포함한 요청은 절대 조용히 쿠키 요청으로 재시도되지 않기 때문입니다. 취소된 키는 취소된 날짜와 마지막으로 사용된 날짜와 함께 목록에 남아 있으며, 이는 유출된 키가 어디까지 도달했는지 파악할 때 필요한 것입니다.

요금제와 한도

무료 요금제는 한 달에 병합된 변경 20건을 작성하고, 하루에 검토하는 병합, 분류하는 피드백, 작성하는 로드맵 카드, 대체 버전을 각각 50건으로 제한합니다. 팀 요금제에는 고정 한도가 없습니다. 설정, 요금제 및 사용량은 제품이 직접 세는 그대로 각 예산을 보여 주고, 무언가가 거부되기 전에 각 예산이 초기화되는 시점을 알려 줍니다. 한도를 넘겨 도착한 작업은 사라지지 않고 보류됩니다. 할당량을 초과한 항목은 받은편지함에서 기다리고, 거부된 로드맵 초안은 기간이 바뀌면 다시 시도할 수 있습니다.

GitLab과 Bitbucket

GitLab 프로젝트나 Bitbucket 저장소는 GitHub 저장소와 같은 방식으로 여러분의 changelog에 데이터를 제공할 수 있습니다: 설정의 GitLab 또는 설정의 Bitbucket에서 연결하고, 당사가 제공하는 webhook을 추가하면(bitbucket.org에서는 Bitbucket 페이지에 해당 버튼이 있으면 Connect with Bitbucket이 대신 추가합니다), 여러분이 지정한 브랜치에 병합된 모든 변경 사항이 동일한 방식으로 작성되고 동일한 사람의 검토를 거치는 검토함의 초안 항목이 됩니다. 항목은 병합된 풀 리퀘스트나 머지 리퀘스트에서 만들어지며, GitHub와 Bitbucket에서는 설정의 What creates drafts에서 푸시 모드를 선택하면 푸시에서도 만들어집니다. GitLab 프로젝트는 머지 리퀘스트에서만 초안을 작성합니다.

프로젝트 연결하기

GitLab 프로젝트는 설정의 GitLab에서, Bitbucket 저장소는 설정의 Bitbucket에서 연결합니다. 경로(GitLab은 acme/web과 같은 그룹과 프로젝트, Bitbucket은 acme/app과 같은 워크스페이스와 저장소)를 입력하면, 당사가 webhook 주소와 시크릿을 반환합니다. 두 가지 모두 상대측의 webhook 설정에 붙여넣으세요: GitLab에서는 Merge request events를 체크하고, Bitbucket에서는 Merged pull request 트리거와 Push repository 트리거를 모두 체크하세요. 자체 호스팅 인스턴스는 https를 통해 작동합니다. 시크릿은 그 순간에 한 번만 표시됩니다. 분실했다면 프로젝트를 제거하고 다시 연결하세요. Bitbucket 페이지에 Connect with Bitbucket 버튼이 표시되는 bitbucket.org에서는 붙여넣기를 건너뛸 수 있습니다: 그 버튼을 누르고 접근을 한 번 허용하면 당사가 저장소의 메인 브랜치를 읽고 webhook을 추가합니다. 저장소 관리자 권한이 필요합니다. 자체 호스팅 Bitbucket이거나 직접 붙여넣고 싶다면 Set it up by hand를 선택하세요. 위와 같이 주소와 시크릿을 받게 됩니다. Bitbucket 저장소를 제거했다가 다시 연결한다면, Bitbucket의 Repository settings, 그다음 Webhooks에서 이전 webhook도 삭제하세요. 프로젝트를 연결한 후에는 해당 프로젝트 행에서 브랜치를 변경하고 자동 게시를 켤 수 있으며, 전송이 무시된 경우 그 행에 이유가 표시됩니다.

왜 Bitbucket은 브랜치를 묻고 GitLab은 묻지 않는가

GitLab은 여러분의 프로젝트가 기본으로 취급하는 브랜치를 당사에 알려주므로 필드를 비워두고 바로 그것을 의미할 수 있습니다. Bitbucket은 기본 브랜치를 전혀 보내지 않으므로, 만약 비워두도록 허용한다면 비교할 대상이 없어 여러분의 webhook은 완벽하게 설치된 것처럼 보이면서도 단 하나의 항목도 생성하지 못한 채로 있게 될 것입니다. 당사는 그런 일이 일어나도록 두기보다는 질문 하나를 하는 쪽을 선택합니다. Connect with Bitbucket을 사용하면 접근을 허용할 때 당사가 Bitbucket에 메인 브랜치를 물어보므로 직접 입력하지 않아도 됩니다.

아직 다루지 않는 것

changelog 항목뿐이며, 그 외에는 없습니다. 여러분을 위해 issue를 제출하는 피드백 위젯, 수정이 전달되었을 때 해당 issue에 다시 게시되는 답글, issue 라벨로 구동되는 공개 roadmap, 검토함의 소스 미리보기는 오늘날 모두 GitHub 전용입니다.

이유는 감추기보다 밝히는 편을 선택한 것입니다. 이들 각각은 당사가 보관하는, 여러분의 프로젝트에 대한 쓰기 권한을 가진 액세스 토큰이 필요합니다. changelog 항목은 어느 것도 필요하지 않습니다. 작성의 근거가 되는 모든 것이 webhook 자체에 도착하기 때문이며, 따라서 GitLab이나 Bitbucket을 webhook으로 연결해도 당사에 어떤 자격증명도 주어지지 않고 여러분의 코드를 읽는 일도 없습니다. Connect with Bitbucket은 유일한 예외입니다. Bitbucket은 단 한 번의 요청에 한해 저장소와 풀 리퀘스트를 읽고 webhook을 관리할 수 있는 토큰을 당사에 빌려주며, 당사는 이를 메인 브랜치를 읽고 webhook을 추가하는 데만 사용한 뒤 폐기합니다. 아무것도 저장되지 않습니다. 당사는 기능 목록을 채우기 위해 토큰을 요구하기보다는 여러분에게 아무 비용도 들지 않는 부분을 제공하는 쪽을 선택합니다.

항목의 다른 버전

하나의 변경 사항은 보통 여러 번 설명되어야 합니다: changelog에서 고객에게, 그에 대한 질문에 답하는 사람에게, 그리고 아무도 네 문단을 읽지 않는 채널에서. 검토함에서 항목을 승인하기 전에 두 가지 추가 버전 중 하나를 작성할 수 있습니다.

공지 버전은 한두 줄이며, 항목을 승인할 때 전체 텍스트 대신 Slack에 게시되는 것입니다. 지원 노트는 사내 브리핑입니다: 무엇이 변경되었는지, 고객이 무엇을 알아차릴지, 그리고 지원 담당자가 거의 그대로 말할 수 있는 문장. 둘 다 사용되기 전에 다시 작성할 수 있는 초안이며, 둘 다 제거할 수 있습니다.

둘 다 게시되지 않습니다

이 버전들은 여러분의 changelog 페이지, 어떤 피드, 위젯, 또는 이들을 제공하는 API에도 전혀 나타나지 않습니다. 특히 지원 노트는 회사 내부 사람들을 위해 작성되며 항목 자체보다 더 직접적일 수 있습니다. 그것이 존재하는 유일한 곳은 여러분의 검토함과, 사용한다면 여러분 자신의 사본입니다.

무엇으로부터 작성되는가

항상 항목으로부터 작성되며, 풀 리퀘스트로부터는 절대 작성되지 않습니다. 이는 의도적입니다: 항목은 이미 보안 수정을 모호하게 유지하는 규칙과 여러분 자신의 검토를 거쳤습니다. 거기서 다시 작성된 버전은 여러분이 제거한 세부 정보를 다시 도입할 수 없습니다. 그 세부 정보는 모델에 주어진 것 안에 없기 때문입니다.

Slack에서 공지하기

항목을 승인하면 공개되는 것과 동시에 Slack 채널에 게시될 수 있습니다. 설정의 Slack 탭에서 연결하세요: 여러분 자신의 워크스페이스에서 수신 webhook을 만들고, 채널을 선택하고, URL을 붙여넣으세요. 그 webhook 외에는 여러분 측에서 아무것도 설치되지 않으며, 당사는 여러분의 워크스페이스에 대한 어떤 접근도 요청하지 않습니다.

메시지는 항목 제목, 승인한 그대로의 텍스트, 카테고리와 태그, 그리고 여러분의 changelog에 있는 항목으로 돌아가는 링크를 포함합니다. Markdown은 Slack이 실제로 렌더링하는 것으로 번역되므로, 항목이 자신의 별표를 그대로 보여주며 도착하는 일은 없습니다.

webhook URL은 자격증명입니다

그 URL을 가진 사람은 누구나 채널에 게시할 수 있으므로 당사는 이를 비밀번호처럼 취급합니다: 저장된 후에는 여러분 자신의 데이터 내보내기를 포함해 어떤 화면이나 API 응답도 다시는 그것을 보여주지 않습니다. 이후 여러분이 보는 것은 마스크이며, 두 webhook을 구별하기에는 충분하지만 다른 누구에게도 쓸모가 없습니다. 당사는 hooks.slack.com 주소만 받아들이므로, 잘못 입력되거나 대체된 URL은 가져오는 대신 거부됩니다.

작동을 멈출 때

Slack에서 앱을 제거하거나 채널을 보관하면 webhook은 영구적으로 작동을 멈춥니다. 당사는 첫 번째 거부된 메시지에서 이를 알아차리고 공지를 끄며, Slack 탭에서 그 이유와 날짜와 함께 표시합니다. 조용히 계속 재시도하지 않는 것은 의도적입니다: 아무도 공지하지 않은 changelog는 아무도 읽지 않은 것과 정확히 똑같이 보이며, 이는 알려줄 가치가 있는 차이입니다.

일시 중지

일시 중지는 공지를 중단하고 webhook을 유지하므로, 재개는 Slack을 다시 거치는 대신 한 번의 클릭입니다. 연결 해제는 URL을 완전히 제거합니다. 어느 쪽이든 게시 자체는 영향을 받지 않습니다: Slack은 여러분의 changelog가 게시하는 채널이지, 기다리는 관문이 아닙니다. 무언가를 승인할 때 Slack에 도달할 수 없다면 항목은 여전히 게시되며 공지는 스스로 재시도됩니다.

RSS와 JSON Feed

GEThttps://api.changeloop.dev/v1/public/YOUR_PUBLIC_ID/rss.xmlGEThttps://api.changeloop.dev/v1/public/YOUR_PUBLIC_ID/feed.json

동일한 게시된 항목을 독자가 이해하는 두 가지 형식, 즉 RSS 2.0과 JSON Feed 1.1로 구독 가능한 피드로 제공합니다. 둘 다 changelog 피드와 동일한 repos, category, tag 필터를 받아들이고 동일한 Cache-Control과 ETag를 가집니다. 어느 쪽도 페이지네이션하지 않습니다: 독자는 피드의 맨 앞을 폴링하므로, 이들은 커서 없이 가장 최근 항목만 반환합니다.

항목 텍스트는 새니타이즈된 HTML이며, RSS에서는 CDATA로 감싸이고 JSON Feed에서는 content_html로 제공됩니다. JSON Feed는 추가로 네임스페이스가 지정된 _changelogapp 확장 아래 여러분의 태그 색상을 포함하지만, RSS는 포함하지 않습니다. 그것들을 칠할 독자가 없기 때문입니다.

호스팅된 페이지는 둘 다를 rel="alternate" 링크로 광고하므로, 그곳에 도달한 브라우저나 독자는 경로를 알려주지 않아도 구독할 수 있습니다.

단독 항목

GEThttps://api.changeloop.dev/v1/public/YOUR_PUBLIC_ID/entries/ENTRY_ID

하나의 게시된 항목을 반환합니다. 이는 changelog 피드가 data 배열에 담아 전달하는 것과 동일한 객체입니다. 피드 안의 영구 링크가 가리키는 곳이 여기이며, ID가 있어서 그것을 찾기 위해 피드를 페이지네이션하고 싶지 않을 때 유용합니다. 알 수 없는 ID, 또는 게시되지 않은 항목에 속한 ID는 다른 알 수 없는 ID와 동일한 본문으로 404를 반환합니다.

markdown 피드

GEThttps://api.changeloop.dev/v1/public/YOUR_PUBLIC_ID/changelog.md

동일한 게시된 항목을 text/markdown으로 제공되는 순수 markdown으로 제공합니다. 이는 브라우저가 아닌 독자를 위해 존재합니다: '이 제품에서 최근 무엇이 바뀌었나요'에 답하는 LLM이나 에이전트는 RSS를 파싱하거나 JSON을 탐색하지 않고 텍스트를 얻습니다. changelog 피드와 동일한 repos, category, tag 필터를 받아들이고, 동일한 Cache-Control과 ETag를 가지며, 다른 둘과 정확히 같은 방식으로 조건부 요청에 304로 응답합니다.

각 항목은 하나의 섹션입니다: 제목이 표제로, 그다음 날짜, 카테고리, 태그를 담은 한 줄, 그다음 작성된 그대로의 항목 텍스트, 그다음 항목에 있다면 Learn more 링크, 그다음 영구 링크. 문서는 여러분의 피드 제목과 설명으로 시작하며 호스팅된 페이지로 다시 연결됩니다. 아직 아무것도 게시되지 않았다면, 빈 본문을 반환하는 대신 한 문장으로 그것을 말하므로 독자는 이를 실패한 가져오기와 구별할 수 있습니다.

호스팅된 페이지는 RSS 및 JSON Feed 링크와 함께 type text/markdown으로 이를 rel="alternate" 링크로 광고하므로, HTML을 가져온 에이전트는 경로를 알려주지 않아도 이를 찾을 수 있습니다.

제공되는 것은 새니타이즈된 HTML이 아니라 당사가 작성하고 여러분이 승인한 markdown입니다. 이는 markdown으로서 안전하며, markdown은 비활성 상태이므로, 이 응답이 결코 text/html이 아닌 이유입니다. 직접 렌더링한다면, 다른 신뢰할 수 없는 markdown을 이스케이프하듯 이스케이프하세요: 공개 저장소에서 작성된 항목은 그곳에서 풀 리퀘스트를 열 수 있는 누구에게나 영향을 받을 수 있습니다.

여러분 자신의 사이트에서 피드백 수집하기

테스트하기 전에 오리진을 추가하세요

이는 제품 내에서 쓰기를 수행하는 유일한 엔드포인트이므로 어디서든 오는 요청을 받아들이지 않습니다. 브라우저의 Origin 헤더를 팀별 허용 목록과 대조하며, 이 목록은 비어 있는 상태로 시작합니다. 비어 있음은 모두 허용이 아니라 모두 거부를 의미합니다. 여러분이 삽입하는 오리진을 추가할 때까지 모든 제출은 {"error":"origin_not_allowed"}와 함께 403을 반환하며, 여러분의 받은편지함에는 아무것도 도착하지 않습니다. 여러분의 폼이 올바르게 보이는데도 계속 실패한다면 거의 항상 이것이 원인입니다. {"allowedOrigins": ["https://your-site.example"]}를 담은 인증된 PATCH를 /v1/settings/feed에 보내 목록을 설정하고, 동일한 경로에 GET을 보내 다시 읽어오세요. 이는 여러분의 publicId, allowedOrigins, 그리고 구독자가 피드 리더에서 보는 feedTitle과 feedDescription으로 응답합니다. 당사는 브라우저가 보낸 정확한 형태로 각 오리진을 저장하므로, 여러분이 보낸 것에 끝에 붙은 슬래시나 명시적인 기본 포트가 있어도 문제가 되지 않습니다.

POST/v1/public/YOUR_PUBLIC_ID/feedback
POST https://api.changeloop.dev/v1/public/YOUR_PUBLIC_ID/feedback
Content-Type: application/json
Origin: https://your-site.example

{ "email": "someone@example.com", "message": "Dark mode, please." }

202 Accepted
{ "publicSubmissionId": "0ZbQ8yqk3n7T1sVJ4mWpLd2rXfEuGh6A" }

email은 이메일 주소처럼 보여야 하며 254자 이하여야 합니다. message는 비어 있지 않아야 하며 문자가 아닌 UTF-8 바이트로 측정하여 2KB 이하여야 합니다. JSON 본문 전체는 8KB로 제한됩니다. 필드가 하나 더 있습니다, website: 이는 허니팟이므로 생략하거나, 당사의 위젯이 하는 것처럼 숨겨진 입력으로 렌더링한다면 비워서 보내세요.

이것으로 무언가를 디버깅하기 전에 허니팟을 이해할 가치가 있습니다. website에 무언가가 적혀 도착하면 당사는 완전히 평범해 보이는 제출 ID와 함께 202를 반환한 후 아무것도 하지 않습니다. 잡혔다는 것을 알게 된 봇은 단순히 다른 방식으로 다시 시도하기 때문입니다. 이는 봇에게는 올바른 응답이고 여러분에게는 혼란스러운 응답이므로, 여러분 자신의 폼에 브라우저가 자동 완성할 수 있는 website라는 이름의 필드가 있다면 이름을 바꾸거나 제거하세요. 받아들여진 것처럼 보이지만 절대 나타나지 않는 제출은 거의 항상 이것입니다.

당사가 받아들인 제출은 publicSubmissionId와 함께 202를 반환합니다. 이를 보낸 사람에게 돌려주고 가능하면 보관하세요: 그것이 이후 무슨 일이 일어났는지 그들이 알 수 있는 유일한 방법입니다.

실패 모드는 잘못된 형식의 경우 invalid_email 또는 invalid_message와 함께 400, 형식은 맞지만 너무 큰 경우 email_too_large 또는 message_too_large와 함께 413, 하나의 주소에서 하나의 피드로 분당 5회 또는 시간당 30회를 초과하는 경우 rate_limited와 함께 429, origin_not_allowed와 함께 403, 그리고 인식하지 못하는 피드 ID의 경우 not_found와 함께 404입니다.

또한 제출이 얼마나 많은 후속 작업을 트리거할 수 있는지에 대한 팀별 일일 한도가 있습니다. 이를 초과해도 도착하는 모든 것을 계속 받아들이고 저장하지만, 스스로 무언가를 여는 대신 팀의 누군가가 볼 때까지 기다립니다.

제출 하나 확인하기

GET/v1/public/YOUR_PUBLIC_ID/feedback/PUBLIC_SUBMISSION_ID

status로 응답하며, 해당 제출에 대한 issue가 존재하면 githubIssueUrl을, 작업이 완료되면 제목과 링크를 담은 shippedEntry를 추가로 반환합니다. 발신자의 이메일 주소는 이 경로를 위해 당사 데이터베이스에서 읽히는 일이 전혀 없으며, 반환되는 것은 더더욱 없습니다. 이는 누구나 볼 수 있는 페이지에서 렌더링해도 안전한 응답을 만듭니다. ID가 자격증명 전체이므로 그렇게 취급하세요. 주소와 피드당 분당 20회, 시간당 200회로 제한됩니다.

캐싱, CORS, 조건부 요청

두 피드 모두 강력한 ETag와 함께 Cache-Control: public, max-age=60, stale-while-revalidate=300을 전송합니다. 그 ETag를 If-None-Match로 다시 보내면 변경되지 않은 피드는 본문 없이 304를 반환합니다. 어떤 응답 필드도 벽시계 값을 담지 않으므로, 변경되지 않은 데이터를 다시 렌더링해도 ETag는 안정적으로 유지되며, 이것이 이 304를 신뢰할 만하게 만드는 이유입니다.

두 피드와 제출 조회는 익명 읽기이며 Access-Control-Allow-Origin: *로 응답하므로 어떤 오리진에서든, curl에서든, 빌드 단계에서든 호출할 수 있습니다. 피드백 POST는 예외입니다: 오리진이 허용되든 아니든 와일드카드가 아닌 여러분 자신의 허용된 오리진과 Vary: Origin으로 응답합니다. 브라우저는 이에 대해 프리플라이트를 수행하며, 프리플라이트는 오리진이 허용되었는지 여부와 관계없이 항상 204로 응답하므로 여러분의 설정을 탐색하는 데 사용할 수 없습니다.