Dokumentasi developer
Terakhir diperbarui 26 September 2026.
Semua yang dipublikasikan Changeloop untukmu adalah JSON sederhana lewat HTTPS. Tidak ada SDK yang perlu dipasang, tidak ada kunci API yang perlu dirotasi, dan tidak ada langkah login: dua feed di bawah adalah bacaan publik anonim yang dikunci dengan ID feedmu. Ganti YOUR_PUBLIC_ID dengan milikmu di contoh mana pun di halaman ini.
Satu hal yang perlu diketahui sebelum mulai: ID feed publikmu ada di dalam aplikasi itu sendiri. Masuk, buka Pengaturan, dan ID itu ada di sana di bagian Feed publik, bagian yang kamu lihat secara default, bersama tautan siap pakai untuk changelog.json dan roadmap.json, tautan ke halaman feed hosting-mu, dan cuplikan kode widget di bawah, masing-masing dengan tombol salinnya sendiri.
Memulai
Lima langkah membawamu dari pendaftaran sampai changelog tampil di situsmu sendiri. Halaman "Get started" di aplikasi memandumu melewatinya dan mencentang setiap langkah begitu selesai.
- Hubungkan sumber: repositori GitHub, proyek GitLab, atau repositori Bitbucket.
- Pilih bahasa yang dipakai untuk menulis entrimu.
- Jika mau, buat tag agar pembaca bisa memfilter menurut area produk.
- Terbitkan entri pertamamu. Perubahan yang digabung masuk ke kotak peninjauan sebagai draf: setujui salah satunya, atau aktifkan publikasi otomatis untuk repositori itu.
- Pasang di situsmu: tautkan halaman yang di-hosting, tempel widget, atau tampilkan feed JSON di halamanmu sendiri.
Changelog-mu dalam sekitar sepuluh baris React
Tempel ini ke dalam komponen dan kamu punya changelog yang berfungsi. Tidak ada lagi yang perlu ditambahkan.
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 adalah markdown yang kami susun, sebagai teks. Jika kamu lebih suka merender output berformat, gunakan htmlContent: dibangun di sisi server oleh sanitizer kami sendiri dari daftar tag dan atribut yang diizinkan yang tetap, dan itulah satu-satunya nilai di respons mana pun ini yang dimaksudkan untuk disuntikkan sebagai markup. Selebihnya adalah teks, dan entri yang disusun dari repositori publik bisa dipengaruhi oleh siapa saja yang bisa membuka pull request di sana, jadi perlakukan sesuai itu.
Feed changelog
GET/v1/public/YOUR_PUBLIC_ID/changelog.jsonEntri yang kamu publikasikan, dari yang terbaru, dengan ID terbaru memutus seri pada stempel waktu yang identik.
Parameter kueri
- repos menerima daftar nama repositori lengkap yang dipisahkan koma, misalnya acme/web,acme/api. Hanya entri dari repositori itu yang dikembalikan. Kosongkan dan kamu dapat semuanya.
- limit adalah berapa banyak entri yang kamu inginkan per halaman. Defaultnya 20, apa pun di atas 50 dibatasi ke 50, dan apa pun yang tidak bisa kami baca sebagai angka positif kembali ke 20 alih-alih gagal.
- cursor bersifat opak. Ambil nilai nextCursor dari respons sebelumnya dan kembalikan apa adanya. Cursor yang tidak bisa kami decode diperlakukan seolah tidak ada cursor, jadi kamu mendapat halaman pertama lagi alih-alih error.
Respons
{
"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" }
}
Setiap entri membawa sembilan kolom yang sama: id, title, mdContent, htmlContent, repoFullName, category, tags, learnMoreUrl, dan publishedAt. category adalah feature, fix, atau internal, dan null jika penyusun tidak menetapkannya, publishedAt adalah string ISO 8601, dan htmlContent adalah string kosong pada entri yang tidak pernah melalui penyusun. tags adalah array nama area produkmu sendiri dan kosong jika tidak ada yang ditetapkan, learnMoreUrl null kecuali peninjau menambahkannya, dan warna untuk setiap tag berasal dari map tagColors pada respons, bukan dari entrinya, jadi tag yang sudah kamu hapus dari kosakatamu cukup dirender tanpa warna. nextCursor null saat kamu mencapai akhir.
ID feed yang tidak dikenal merespons 404 dengan {"error":"not_found"}, begitu juga yang salah format. Keduanya sengaja dibuat tidak bisa dibedakan, jadi endpoint ini tidak bisa digunakan untuk mengetahui ID mana yang ada.
Feed roadmap
GET/v1/public/YOUR_PUBLIC_ID/roadmap.jsonTiga kolom yang sama yang sudah dijaga timmu secara manual.
{
"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 adalah array, bukan objek yang diindeks nama kolom, dan urutannya adalah bagian dari kontrak: planned, lalu building, lalu shipped. Ketiganya selalu ada, termasuk yang kosong, jadi kamu tidak pernah perlu membedakan "kolom itu tidak ada" dari "belum ada isinya". Render dalam urutan yang kamu terima dan kamu akan cocok dengan setiap permukaan lain yang kami bangun.
Sebuah item punya tepat lima kolom: id, column, publicTitle, publicDescription, dan publishedAt. publicDescription selalu string dan bisa kosong, tidak pernah null. Tidak ada apa pun tentang issue asal item itu yang diekspos di sini, bukan repositorinya maupun nomor issue-nya, dan itu disengaja, bukan kelalaian yang akan kami lengkapi nanti.
Endpoint ini tidak menerima parameter kueri apa pun. Tidak ada cursor, limit, atau filter repositori, karena roadmap adalah papan kecil yang dikurasi seseorang, bukan log yang terus tumbuh selamanya. Setiap kolom mengembalikan hingga 50 item dan mengatur hasMore jika ada lebih dari itu. hasMore bersifat informatif: tidak ada cursor untuk mengikutinya, jadi jangan bangun paginasi di sekitarnya.
publicTitle dan publicDescription adalah teks biasa yang disusun dari judul dan isi issue, yang di repositori publik bisa dipengaruhi siapa saja yang membuka issue. Tidak membawa jaminan sanitasi HTML dan bukan pengecualian htmlContent. Render sebagai teks.
Widget yang bisa disematkan
Jika kamu lebih suka tidak membangun apa pun, masukkan dua baris ini. Widget adalah custom element yang dirender di dalam shadow root, jadi tidak mewarisi gayamu maupun bocor ke sana.
<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>
Kedua atribut wajib diisi. data-public-id adalah ID feedmu, data-api adalah origin tempat widget mengambil data. Jika salah satu hilang, elemen menulis error ke konsol dan tidak merender apa pun, itulah hal pertama yang perlu diperiksa jika kamu melihat ruang kosong di tempat seharusnya widget berada.
Tambahkan data-theme="dark" pada elemen untuk tampilan gelap; halamanmu bisa mengubahnya saat berjalan. Untuk penataan lebih dalam, widget menyediakan properti kustom CSS (--changelogapp-text, --changelogapp-bg, --changelogapp-accent, dan lainnya) serta nama ::part(), yang kamu atur di stylesheet-mu sendiri. Aplikasi menampilkan pratinjau langsung kedua tema di Pengaturan, Feed publik.
Tambahkan data-repos untuk menampilkan hanya sebagian repositori Anda, misalnya changelog satu produk di situs produk itu ketika beberapa produk berbagi satu akun. Nilainya adalah daftar nama lengkap owner/repo yang dipisahkan koma; nama tanpa pemilik tidak cocok dengan apa pun dan menampilkan feed kosong tanpa galat. Paling banyak sepuluh repositori yang diperhitungkan. Widget yang dibatasi hanya menampilkan Updates dan Feedback, karena roadmap tidak punya tampilan per repositori, dan masukan tetap diajukan ke tempat target masukan tim Anda menunjuk. Di Pengaturan, Feed publik ada pemilih yang menuliskan atribut ini untuk Anda.
Ini merender tiga tab dalam urutan ini: Updates, Roadmap, dan Feedback. Dua yang pertama membaca feed di atas. Yang ketiga mengirim ke endpoint di bawah dan menyimpan ID setiap kiriman di localStorage, jadi pengunjung bisa kembali dan melihat apa yang terjadi pada yang mereka kirim.
Skrip disajikan dengan versi. /widget.js selalu menyajikan build terbaru dan di-cache selama satu jam, jadi rilis sampai ke pengunjungmu tanpa kamu menyentuh apa pun. /widget-vN.js mengunci satu build: begitu nomor versi disajikan, byte-nya tidak pernah berubah lagi, dan di-cache selama satu tahun. Kunci jika kamu lebih suka mengadopsi perubahan dengan sengaja.
Muat tepat satu skrip widget per halaman
Kedua URL adalah alternatif, bukan lapisan. Keduanya mendaftarkan nama custom element yang sama, dan browser hanya mengizinkan sebuah nama didaftarkan sekali per dokumen: mana pun yang dieksekusi lebih dulu menang, selama umur halaman itu, dan yang kedua menjadi tidak aktif. Jadi halaman yang membawa /widget.js dan /widget-v5.js merender mana pun yang dieksekusi browser lebih dulu, yang bukan sesuatu yang kamu kendalikan, dan menambahkan /widget-v5.js di samping /widget.js yang sudah ada untuk mengunci versi tidak melakukan apa-apa.
Saat itu terjadi, widget menulis peringatan ke konsol yang menyebutkan kedua build, jadi kamu tidak dibiarkan menebak-nebak. Ini tidak bisa melakukan lebih dari sekadar memperingatkan: saat salinan kedua dieksekusi, yang pertama sudah mengklaim namanya. Perbaikannya selalu mengganti tag skrip alih-alih menambahkan yang lain, dan hal yang sama berlaku jika pengelola tag atau sebuah parsial menyisipkannya untukmu. Untuk beralih dari build yang berjalan terus ke yang terkunci, ubah src-nya.
Halaman feed hosting
https://feed.changeloop.dev/feed/YOUR_PUBLIC_IDKami juga menghosting halaman sederhana di alamat itu: changelog dan papan roadmap-mu, dirender dari dua feed yang sama di atas. Tidak perlu login dan tidak perlu apa pun yang dikonfigurasi di sisimu. Ini juga tempat kami mengirim orang kembali begitu sebuah lingkaran ditutup: komentar Shipped yang kami tinggalkan pada issue GitHub mengarah ke sini, begitu juga shippedEntry.link dari pencarian kiriman di atas, keduanya mendarat di entri yang dikirim dengan anchor #entry-ID-nya sendiri, yang tetap menemukan entrinya bahkan jika sudah pindah ke halaman berikutnya.
Perlakukan ini sebagai cadangan, bukan integrasi. Feed changelog dan widget tetap cara untuk menaruh ini di situsmu sendiri agar terlihat seperti produkmu bukan produk kami; halaman ini untuk saat kamu belum melakukannya, dan untuk tautan penutup lingkaran, yang mengarah ke sini terlepas dari apa lagi yang sudah kamu bangun.
Domainmu sendiri
Kamu bisa menyajikan halaman yang dihosting dari alamatmu sendiri, tanpa perubahan DNS atau sertifikat. Di Pengaturan, Domain kustom, tempel alamat publik yang akan dilihat pembacamu (misalnya https://example.com/changelog), lalu arahkan path itu di situsmu ke target proxy yang ditampilkan di sana: satu aturan mencakup halaman, aset, data, dan feed-nya. Periksa domain saya mengambil alamatmu dari sisi kami dan memberi tahu apakah proxy sudah benar dan, jika tidak, apa yang harus diubah.
Server MCP
POSThttps://api.changeloop.dev/mcpJika kamu bekerja di Claude Code, ChatGPT, atau agen lain yang berbicara Model Context Protocol, kamu bisa menghubungkannya langsung ke changelog-mu. Agen itu kemudian bisa melihat apa yang menunggu peninjauan, mengedit teksnya, dan mempublikasikan, tanpa kamu meninggalkan editor. Ini adalah gerbang peninjauan yang sama seperti aplikasi web: tidak ada yang menjadi publik tanpa sesuatu yang menyetujuinya.
Menghubungkan Claude Code
Buat kunci API terlebih dahulu (Pengaturan, Kunci API), lalu tambahkan server dengan kuncimu di header:
claude mcp add --transport http changeloop \
https://api.changeloop.dev/mcp \
--header "Authorization: Bearer clapi_YOUR_KEY"
Untuk klien yang membaca konfigurasi JSON, hal yang sama terlihat seperti ini:
{
"mcpServers": {
"changeloop": {
"type": "http",
"url": "https://api.changeloop.dev/mcp",
"headers": { "Authorization": "Bearer clapi_YOUR_KEY" }
}
}
}
Belum ada alur OAuth. Autentikasinya adalah kunci API di header, yang dilakukan oleh dua perintah di atas. Mencabut kunci itu di Pengaturan memutus koneksi agen pada permintaan berikutnya.
Apa yang bisa dilakukan agen
Tujuh alat, dan daftarnya sengaja dibuat singkat. Apa pun lagi yang bisa dilakukan produk ini bisa dijangkau lewat REST API dengan kunci yang sama; setiap alat yang diekspos ke agen adalah satu hal lagi yang bisa dibujuk untuk dipanggilnya.
- list_pending_entries, list_published_entries, get_entry - membaca entrimu. Yang menunggu tidak bersifat publik.
- update_entry - mengubah judul atau isi markdown sebuah entri. HTML yang disajikan feed dirender ulang dari markdown-mu oleh sanitizer kami; agen tidak bisa memberikan HTML.
- approve_entry - mempublikasikan. Ini bersifat publik dan langsung, dan memberi tahu umpan balik apa pun yang tertaut di GitHub. Hanya entri yang menunggu yang bisa disetujui.
- discard_entry - menjaga entri tetap di luar changelog. Bisa dibatalkan dari aplikasi web.
- get_changelog_info - ID feedmu dan alamat tempat changelog-mu disajikan.
Apa yang tidak bisa dilakukan
Setiap alat dibatasi cakupannya pada tim tempat kunci itu berasal, dan tidak satu pun menerima tim sebagai argumen, jadi tidak ada apa pun yang bisa mengarah ke tim lain bahkan jika sesuatu mencoba. Server tidak menerima sesi browser, hanya kunci: sebuah permintaan harus melampirkan kredensial dengan sengaja. Dan kunci tidak bisa mengelola kunci atau mengunduh ekspor datamu, jadi agen yang terhubung dengan cara ini tidak bisa membuat kredensial kedua untuk dirinya sendiri atau menarik keluar datamu dalam satu panggilan.
Kunci API
Semua di atas bersifat anonim dan tidak memerlukan kredensial. API yang terautentikasi - pengaturanmu, kotak peninjauanmu - adalah permukaan yang berbeda, dan menerima sesi browser yang masuk atau kunci API. Kunci untuk skrip dan agen: apa pun yang perlu menjangkau changelog-mu tanpa orang di keyboard.
Authorization: Bearer clapi_YOUR_KEYBuat satu di aplikasi di bawah Pengaturan, di tab Kunci API. Kunci itu ditampilkan sekali, saat kamu membuatnya, dan tidak pernah lagi: kami hanya menyimpan hash-nya, jadi tidak ada layar mana pun yang bisa menampilkannya kepadamu untuk kedua kalinya. Jika hilang, cabut dan buat yang baru.
Apa yang bisa dan tidak bisa dilakukan kunci
Kunci membawa akses yang sama dengan login, dibatasi cakupannya pada satu tim tempat ia dibuat, dengan dua pengecualian yang disengaja. Tidak bisa mengelola kunci API, dan tidak bisa mengunduh ekspor datamu. Keduanya memerlukan login sungguhan, agar kunci yang bocor tidak bisa membuat pengganti untuk dirinya sendiri, tidak bisa mencabut kunci yang akan kamu gunakan untuk menguncinya, dan tidak bisa menarik keluar data timmu dalam satu permintaan.
Mencabut
Pencabutan berlaku pada permintaan berikutnya. Kunci yang dicabut merespons 401 persis seperti yang tidak dikenal, dan terus merespons 401 bahkan dari browser yang masih memegang sesi yang valid, karena permintaan yang membawa header Authorization tidak pernah diam-diam dicoba ulang sebagai permintaan cookie. Kunci yang dicabut tetap terdaftar dengan tanggal pencabutannya dan tanggal terakhir digunakan, itulah yang kamu inginkan saat mencari tahu ke mana kunci yang bocor telah menjangkau.
Paket dan batasan
Paket gratis menulis 20 perubahan yang digabung per bulan dan membatasi jumlah harian penggabungan yang diperiksa, kiriman umpan balik yang ditriase, kartu roadmap yang disusun, dan versi alternatif masing-masing 50; paket tim tidak punya batas keras. Pengaturan, Paket dan penggunaan menampilkan setiap anggaran persis seperti yang dihitung produk, beserta saat anggaran itu direset, sebelum ada yang ditolak. Pekerjaan yang datang melebihi batas ditahan, bukan hilang: entri yang melebihi kuota menunggu di kotak masuk, dan draf roadmap yang ditolak bisa dicoba lagi setelah jendelanya berganti.
GitLab dan Bitbucket
Sebuah proyek GitLab atau repositori Bitbucket bisa memberi makan changelog-mu dengan cara yang sama seperti repositori GitHub: hubungkan di Pengaturan, lalu GitLab, atau Pengaturan, lalu Bitbucket, tambahkan webhook yang kami berikan (atau, di bitbucket.org, biarkan Connect with Bitbucket yang menambahkannya jika halaman Bitbucket menampilkan tombol itu), dan setiap perubahan yang digabungkan ke branch yang kamu sebutkan menjadi entri draf di kotak peninjauanmu, ditulis dengan cara yang sama dan dijaga oleh peninjauan manusia yang sama. Entri berasal dari pull request atau merge request yang digabungkan, atau, di GitHub dan Bitbucket, dari push jika kamu memilih mode push di Pengaturan, lalu What creates drafts. Proyek GitLab membuat draf hanya dari merge request.
Menghubungkan proyek
Proyek GitLab dihubungkan di Pengaturan, lalu GitLab, dan repositori Bitbucket di Pengaturan, lalu Bitbucket. Masukkan jalurnya (di GitLab grup dan proyek, seperti acme/web; di Bitbucket workspace dan repositori, seperti acme/app) dan kami akan memberikan alamat webhook dan rahasia. Tempel keduanya di pengaturan webhook di sisi mereka: di GitLab centang Merge request events, di Bitbucket centang pemicu Merged pull request dan Push repository. Instansi yang dikelola sendiri berfungsi, lewat https. Rahasianya ditampilkan sekali, saat itu. Jika hilang, hapus proyeknya dan hubungkan lagi. Di bitbucket.org, jika halaman Bitbucket menampilkan tombol Connect with Bitbucket, kamu bisa melewati langkah tempel: tekan tombol itu, izinkan akses sekali, lalu kami membaca branch utama repositori dan menambahkan webhook untukmu. Kamu perlu hak admin di repositori tersebut. Untuk Bitbucket yang dikelola sendiri, atau jika kamu lebih suka menempel, pilih Set it up by hand dan kamu mendapat alamat serta rahasia seperti di atas. Jika kamu menghapus repositori Bitbucket lalu menghubungkannya lagi, hapus juga webhook lamanya di Bitbucket, di Repository settings, lalu Webhooks. Setelah proyek terhubung, kamu bisa mengganti branch-nya dan menyalakan publikasi otomatis di barisnya, dan jika sebuah pengiriman diabaikan, baris itu menjelaskan alasannya.
Mengapa Bitbucket meminta branch dan GitLab tidak
GitLab memberi tahu kami branch mana yang dianggap proyekmu sebagai default, jadi kamu bisa membiarkan kolomnya kosong dan itu berarti demikian. Bitbucket sama sekali tidak mengirim branch default, jadi jika kami membiarkanmu mengosongkannya, kami tidak akan punya apa pun untuk dibandingkan dan webhook-mu akan terlihat terpasang sempurna tanpa pernah menghasilkan satu entri pun. Kami lebih memilih menanyakan satu pertanyaan daripada membiarkan itu terjadi. Dengan Connect with Bitbucket, kami menanyakan branch utama ke Bitbucket saat kamu mengizinkan akses, jadi kamu tidak perlu mengetiknya.
Apa yang belum dicakup
Entri changelog, dan tidak ada lagi. Widget umpan balik yang mengajukan issue untukmu, balasan yang diposting kembali di issue itu saat perbaikannya dikirim, roadmap publik yang digerakkan oleh label issue, dan pratinjau sumber di kotak peninjauan semuanya hanya untuk GitHub saat ini.
Alasannya adalah sesuatu yang lebih kami pilih untuk dinyatakan daripada ditutup-tutupi. Masing-masing itu memerlukan token akses dengan izin tulis ke proyekmu, disimpan oleh kami. Entri changelog tidak memerlukan satu pun, karena semua yang menjadi sumber penulisannya datang di webhook itu sendiri, jadi menghubungkan GitLab atau Bitbucket lewat webhook tidak memberi kami kredensial apa pun dan tidak memberi kami pembacaan kodemu. Connect with Bitbucket adalah satu-satunya pengecualian. Bitbucket meminjamkan kami token yang dapat membaca repositori beserta pull request-nya dan mengelola webhook-nya untuk satu permintaan saja, yang kami pakai hanya untuk membaca branch utama dan menambahkan webhook, lalu kami buang. Tidak ada yang disimpan. Kami lebih memilih mengirimkan bagian yang tidak membebanimu apa pun daripada meminta token hanya untuk melengkapi daftar fitur.
Versi lain dari sebuah entri
Satu perubahan biasanya harus dijelaskan lebih dari sekali: kepada pelanggan di changelog, kepada siapa pun yang menjawab pertanyaan tentangnya, dan di kanal tempat tidak ada yang membaca empat paragraf. Dari kotak peninjauan kamu bisa menyusun salah satu dari dua versi tambahan sebuah entri sebelum menyetujuinya.
Versi pengumuman adalah satu atau dua baris, dan itu yang diposting ke Slack saat kamu menyetujui entrinya, menggantikan teks lengkap. Catatan dukungan adalah briefing internal: apa yang berubah, apa yang akan diperhatikan pelanggan, dan sebuah kalimat yang bisa dikatakan agen hampir kata demi kata. Keduanya adalah draf yang bisa kamu tulis ulang sebelum digunakan, dan keduanya bisa dihapus.
Tidak ada satu pun yang dipublikasikan
Versi-versi ini tidak pernah muncul di halaman changelog-mu, di feed mana pun, di widget, atau di API yang menyajikannya. Catatan dukungan khususnya ditulis untuk orang-orang di dalam perusahaanmu dan mungkin lebih langsung daripada entrinya sendiri. Satu-satunya tempat itu ada adalah kotak peninjauanmu dan, jika kamu menggunakannya, salinanmu sendiri.
Dari apa mereka ditulis
Selalu dari entrinya, tidak pernah dari pull request. Itu disengaja: entrinya sudah melewati aturan yang menjaga perbaikan keamanan tetap samar, dan melalui peninjauanmu sendiri. Versi yang ditulis ulang darinya tidak bisa memunculkan kembali detail yang kamu hapus, karena detail itu tidak ada dalam apa yang diberikan ke model.
Mengumumkan di Slack
Setujui sebuah entri dan itu bisa diposting ke kanal Slack pada saat yang sama saat menjadi publik. Hubungkan di Pengaturan, di tab Slack: buat webhook masuk di workspace-mu sendiri, pilih kanalnya, dan tempel URL-nya. Tidak ada yang dipasang di sisimu selain webhook itu, dan kami tidak meminta akses apa pun ke workspace-mu.
Pesannya membawa judul entri, teks seperti yang kamu setujui, kategori dan tag-nya, dan tautan kembali ke entri di changelog-mu. Markdown diterjemahkan menjadi apa yang benar-benar dirender Slack, jadi entri tidak muncul dengan tanda bintangnya sendiri.
URL webhook adalah kredensial
Siapa pun yang memegang URL itu bisa memposting ke kanalnya, jadi kami memperlakukannya seperti kata sandi: disimpan, dan setelah itu tidak ada layar dan tidak ada respons API yang menampilkannya lagi, termasuk ekspor datamu sendiri. Yang kamu lihat setelahnya adalah topeng, cukup untuk membedakan dua webhook dan tidak berguna bagi orang lain. Kami hanya menerima alamat hooks.slack.com, jadi URL yang salah ketik atau diganti ditolak alih-alih diambil.
Saat berhenti bekerja
Jika kamu menghapus aplikasi di Slack atau mengarsipkan kanalnya, webhook berhenti bekerja secara permanen. Kami menyadarinya pada pesan pertama yang ditolak, mematikan pengumuman, dan mencantumkannya di tab Slack dengan alasan dan tanggalnya. Kami sengaja tidak terus mencoba lagi diam-diam: changelog yang tidak diumumkan siapa pun terlihat persis seperti yang tidak dibaca siapa pun, dan itu perbedaan yang layak diberi tahu.
Menjeda
Jeda menghentikan pengumuman dan mempertahankan webhook, jadi melanjutkan hanya satu klik alih-alih putaran lain lewat Slack. Putuskan menghapus URL-nya sepenuhnya. Bagaimanapun, publikasi itu sendiri tidak terpengaruh: Slack adalah kanal tempat changelog-mu memposting, bukan gerbang yang ditunggunya. Jika Slack tidak bisa dijangkau saat kamu menyetujui sesuatu, entrinya tetap dipublikasikan dan pengumumannya dicoba lagi sendiri.
RSS dan JSON Feed
GEThttps://api.changeloop.dev/v1/public/YOUR_PUBLIC_ID/rss.xmlGEThttps://api.changeloop.dev/v1/public/YOUR_PUBLIC_ID/feed.jsonEntri yang sama yang dipublikasikan sebagai feed yang bisa dilanggan, dalam dua format yang dipahami pembaca: RSS 2.0 dan JSON Feed 1.1. Keduanya menerima filter repos, category, dan tag yang sama seperti feed changelog dan membawa Cache-Control serta ETag yang sama. Tidak ada yang berhalaman: pembaca menyurvei bagian atas feed, jadi ini hanya mengembalikan entri terbaru, tanpa cursor.
Teks entri adalah HTML yang disanitasi, dibungkus dalam CDATA untuk RSS dan sebagai content_html untuk JSON Feed. JSON Feed juga membawa warna tagmu di bawah ekstensi ber-namespace _changelogapp; RSS tidak, karena tidak ada pembaca yang akan mewarnainya.
Halaman hosting mengiklankan keduanya sebagai tautan rel="alternate", jadi browser atau pembaca yang mendarat di sana bisa berlangganan tanpa perlu diberi tahu jalurnya.
Satu entri sendirian
GEThttps://api.changeloop.dev/v1/public/YOUR_PUBLIC_ID/entries/ENTRY_IDMengembalikan satu entri yang dipublikasikan, objek yang sama yang dibawa feed changelog dalam array data-nya. Ke sinilah tautan permanen di feed mengarah, dan berguna saat kamu punya ID dan tidak ingin memaginasi feed untuk menemukannya. ID yang tidak dikenal, atau milik entri yang belum dipublikasikan, mengembalikan 404 dengan isi yang sama seperti ID tidak dikenal lainnya.
Feed markdown
GEThttps://api.changeloop.dev/v1/public/YOUR_PUBLIC_ID/changelog.mdEntri publikasi yang sama sebagai markdown biasa, disajikan sebagai text/markdown. Ini ada untuk pembaca yang bukan browser: LLM atau agen yang menjawab "apa yang baru berubah di produk ini" mendapat teksnya tanpa mengurai RSS atau menyusuri JSON. Menerima filter repos, category, dan tag yang sama seperti feed changelog, membawa Cache-Control dan ETag yang sama, dan merespons 304 pada permintaan bersyarat persis seperti dua lainnya.
Setiap entri adalah sebuah bagian: judul sebagai heading, lalu satu baris yang membawa tanggal, kategori, dan tag mana pun, lalu teks entri seperti yang ditulis, lalu tautan Learn more jika entrinya punya, lalu tautan permanennya. Dokumennya dibuka dengan judul dan deskripsi feedmu dan mengarah kembali ke halaman hosting. Saat belum ada yang dipublikasikan, ini mengatakannya dalam satu kalimat alih-alih mengembalikan isi kosong, jadi pembaca bisa membedakannya dari pengambilan yang gagal.
Halaman hosting mengiklankannya sebagai tautan rel="alternate" dengan type text/markdown, di samping tautan RSS dan JSON Feed, jadi agen yang mengambil HTML bisa menemukannya tanpa perlu diberi tahu jalurnya.
Yang disajikannya adalah markdown yang kami susun dan kamu setujui, bukan HTML yang disanitasi. Itu aman sebagai markdown, yang inert, dan itulah mengapa respons ini tidak pernah text/html. Jika kamu merendernya sendiri, escape seperti kamu meng-escape markdown tidak tepercaya lainnya: entri yang disusun dari repositori publik bisa dipengaruhi oleh siapa saja yang bisa membuka pull request di sana.
Mengumpulkan umpan balik dari situsmu sendiri
Tambahkan origin-mu sebelum mengujinya
Ini satu-satunya endpoint di produk yang menulis, jadi tidak menerima permintaan dari mana pun. Ini mencocokkan header Origin browser dengan daftar izin per tim, dan daftar itu mulai kosong. Kosong berarti tolak semua, bukan izinkan semua. Sampai kamu menambahkan origin tempat kamu menyematkan, setiap kiriman kembali 403 dengan {"error":"origin_not_allowed"} dan tidak ada yang sampai ke kotak masukmu. Jika formulirmu terlihat benar dan masih gagal, hampir selalu ini alasannya. Atur daftarnya dengan PATCH yang terautentikasi ke /v1/settings/feed yang membawa {"allowedOrigins": ["https://your-site.example"]}, dan baca kembali dengan GET ke jalur yang sama, yang merespons dengan publicId-mu, allowedOrigins-mu, dan feedTitle serta feedDescription yang dilihat pelangganmu di pembaca feed. Kami menyimpan setiap origin persis dalam bentuk yang dikirim browser, jadi garis miring di akhir atau port default eksplisit dalam yang kamu kirim tidak masalah.
POST/v1/public/YOUR_PUBLIC_ID/feedbackPOST 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 harus terlihat seperti alamat email dan 254 karakter atau kurang. message harus tidak kosong dan 2KB atau kurang, diukur dalam byte UTF-8 bukan karakter. Isi JSON secara keseluruhan dibatasi 8KB. Ada satu kolom lagi, website: itu adalah honeypot, jadi biarkan saja, atau kirim kosong jika kamu merendernya sebagai input tersembunyi seperti yang dilakukan widget kami.
Honeypot ini layak dipahami sebelum kamu men-debug apa pun dengannya. Jika website tiba dengan sesuatu tertulis di dalamnya, kami merespons 202 dengan ID kiriman yang terlihat sangat biasa dan kemudian tidak melakukan apa-apa, karena bot yang tahu dirinya tertangkap hanya akan mencoba lagi dengan cara berbeda. Itu jawaban yang tepat untuk bot dan membingungkan untukmu, jadi jika formulirmu sendiri punya kolom bernama website yang mungkin diisi otomatis browser, ganti namanya atau hapus. Kiriman yang tampak diterima dan tidak pernah muncul hampir selalu ini.
Kiriman yang kami terima mengembalikan 202 dengan publicSubmissionId. Berikan itu kembali ke orang yang mengirimnya dan simpan jika bisa: itu satu-satunya cara mereka bisa melihat apa yang terjadi selanjutnya.
Mode kegagalannya adalah 400 dengan invalid_email atau invalid_message untuk bentuk yang salah, 413 dengan email_too_large atau message_too_large untuk bentuk yang benar tapi terlalu besar, 429 dengan rate_limited melebihi 5 kiriman per menit atau 30 per jam dari satu alamat ke satu feed, 403 dengan origin_not_allowed, dan 404 dengan not_found untuk ID feed yang tidak kami kenali.
Ada juga batas harian per tim untuk berapa banyak pekerjaan hilir yang bisa dipicu kiriman. Melewati itu kami tetap menerima dan menyimpan semua yang masuk, hanya saja itu menunggu seseorang di timmu untuk melihatnya alih-alih membuka apa pun sendirian.
Memeriksa satu kiriman
GET/v1/public/YOUR_PUBLIC_ID/feedback/PUBLIC_SUBMISSION_IDMerespons dengan status, ditambah githubIssueUrl begitu ada issue untuk kiriman itu, ditambah shippedEntry yang membawa judul dan tautan begitu pekerjaannya keluar. Alamat email pengirim tidak pernah dibaca dari database kami untuk rute ini, apalagi dikembalikan, itulah yang membuat responsnya aman untuk dirender di halaman yang bisa dilihat siapa saja. ID itu adalah seluruh kredensialnya, perlakukan sebagai itu. Dibatasi 20 permintaan per menit dan 200 per jam per alamat dan feed.
Cache, CORS, dan permintaan bersyarat
Kedua feed mengirim Cache-Control: public, max-age=60, stale-while-revalidate=300 bersama ETag yang kuat. Kirim kembali ETag itu sebagai If-None-Match dan feed yang tidak berubah merespons 304 tanpa isi. Tidak ada kolom respons yang membawa nilai jam dinding, jadi ETag tetap stabil saat kami merender ulang data yang tidak berubah, itulah yang membuat 304 itu layak diandalkan.
Kedua feed dan pencarian kiriman adalah bacaan anonim dan merespons dengan Access-Control-Allow-Origin: *, jadi kamu bisa memanggilnya dari origin mana pun, dari curl, atau dari langkah build. POST umpan balik adalah pengecualiannya: merespons dengan origin yang diizinkan sendiri dan Vary: Origin, tidak pernah dengan wildcard. Browser melakukan preflight padanya, dan preflight selalu merespons 204 terlepas dari apakah origin-nya diizinkan, jadi tidak bisa digunakan untuk menyelidiki pengaturanmu.