Geliştirici dokümanları
Son güncelleme 26 Eylül 2026.
Changeloop'un senin için yayımladığı her şey HTTPS üzerinden sade JSON'dur. Kurulacak bir SDK, döndürülecek bir API anahtarı veya bir giriş adımı yoktur: aşağıdaki iki akış, akış kimliğinle anahtarlanan anonim herkese açık okumalardır. Bu sayfadaki herhangi bir örnekte YOUR_PUBLIC_ID'yi kendi kimliğinle değiştir.
Başlamadan önce bilmen gereken bir şey: herkese açık akış kimliğin uygulamanın kendisinde bulunur. Giriş yap, Ayarlar'ı aç, varsayılan olarak geldiğin Herkese açık akış bölümünde hazır changelog.json ve roadmap.json bağlantıları, barındırılan akış sayfana bir bağlantı ve her birinin kendi kopyalama düğmesi olan widget kod parçasıyla birlikte tam orada.
Başlarken
Kayıttan kendi sitende bir changelog'a beş adımda ulaşırsın. Uygulamadaki "Get started" sayfası seni adım adım yönlendirir ve biten her adımı işaretler.
- Bir kaynak bağla: bir GitHub deposu, bir GitLab projesi veya bir Bitbucket deposu.
- Kayıtlarının yazılacağı dili seç.
- İstersen okuyucuların ürün alanına göre filtreleyebilmesi için etiketler oluştur.
- İlk kaydını yayınla. Birleştirilen değişiklikler inceleme kutusuna taslak olarak gelir: birini onayla ya da o depo için otomatik yayınlamayı aç.
- Sitene ekle: barındırılan sayfana bağlantı ver, widget'ı yapıştır ya da JSON akışını kendi sayfanda göster.
Yaklaşık on satır React ile changelog'un
Bunu bir bileşene yapıştır, çalışan bir changelog'un olsun. Eklenecek başka bir şey yok.
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, metin olarak hazırladığımız markdown'dır. Biçimlendirilmiş çıktı renderlamak istersen bunun yerine htmlContent kullan: sabit bir izin verilen etiket ve öznitelik listesinden kendi arındırıcımız tarafından sunucu tarafında oluşturulur ve bu yanıtların herhangi birindeki, işaretleme olarak eklenmesi amaçlanan tek değerdir. Geri kalan her şey metindir ve herkese açık bir depodan hazırlanan kayıtlar orada bir pull request açabilen herkes tarafından etkilenebilir, buna göre davran.
Changelog akışı
GET/v1/public/YOUR_PUBLIC_ID/changelog.jsonYayımlanan kayıtların, en yeniden en eskiye, aynı zaman damgasında beraberliği en yeni kimliğin bozduğu hâli.
Sorgu parametreleri
- repos, örneğin acme/web,acme/api gibi virgülle ayrılmış tam depo adları listesi alır. Yalnızca o depolardan kayıtlar döner. Boş bırakırsan hepsini alırsın.
- limit, sayfa başına kaç kayıt istediğindir. Varsayılan 20'dir, 50'nin üzerindeki her şey 50'ye sabitlenir ve pozitif bir sayı olarak ayrıştıramadığımız her şey hata vermek yerine 20'ye döner.
- cursor opaktır. Önceki yanıttaki nextCursor değerini al ve olduğu gibi geri gönder. Çözemediğimiz bir cursor, cursor yokmuş gibi ele alınır, böylece bir hata yerine yine ilk sayfayı alırsın.
Yanıt
{
"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" }
}
Her kayıt aynı dokuz anahtarı taşır: id, title, mdContent, htmlContent, repoFullName, category, tags, learnMoreUrl ve publishedAt. category feature, fix veya internal'den biridir ve hazırlayan biri belirlemediyse null'dır, publishedAt bir ISO 8601 dizesidir ve htmlContent, hiç hazırlayıcıdan geçmemiş bir kayıtta boş bir dizedir. tags, kendi ürün alanı adlarından oluşan bir dizidir ve hiçbiri atanmadıysa boştur, learnMoreUrl bir incelemeci eklemediyse null'dır ve her etiketin çizileceği renk kayıttan değil yanıttaki tagColors haritasından gelir, böylece kelime dağarcığından kaldırdığın bir etiket sadece renksiz renderlanır. Sona ulaştığında nextCursor null'dır.
Bilinmeyen bir akış kimliği {"error":"not_found"} ile 404 yanıtı verir, hatalı biçimli bir tane de öyle. İkisi kasıtlı olarak ayırt edilemez, böylece bu endpoint hangi kimliklerin var olduğunu anlamak için kullanılamaz.
Roadmap akışı
GET/v1/public/YOUR_PUBLIC_ID/roadmap.jsonEkibinin elle tuttuğu aynı üç sütun.
{
"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, sütun adıyla anahtarlanan bir nesne değil bir dizidir ve sırası sözleşmenin bir parçasıdır: önce planned, sonra building, sonra shipped. Boş olanlar dahil üçü de her zaman oradadır, böylece "böyle bir sütun yok"u "içinde henüz bir şey yok"tan hiç ayırt etmen gerekmez. Aldığın sırada renderla, böylece oluşturduğumuz diğer her yüzeyle eşleşirsin.
Bir öğe tam olarak beş anahtar taşır: id, column, publicTitle, publicDescription ve publishedAt. publicDescription her zaman bir dizedir ve boş olabilir, asla null olmaz. Öğenin geldiği issue hakkında burada hiçbir şey açığa çıkmaz, ne depo ne de issue numarası, ve bu ihmal değil kasıtlıdır.
Bu endpoint hiçbir sorgu parametresi almaz. Cursor, limit veya depo filtresi yoktur, çünkü bir roadmap sonsuza kadar büyüyen bir günlük değil bir kişinin küratörlük ettiği küçük bir panodur. Her sütun en fazla 50 öğe döndürür ve daha fazlası varsa hasMore'u ayarlar. hasMore bilgilendiricidir: onu takip edecek bir cursor yoktur, bu yüzden etrafına bir sayfalayıcı kurma.
publicTitle ve publicDescription, herkese açık bir depoda bir issue açabilen herkesin etkileyebileceği issue başlıklarından ve gövdelerinden hazırlanan sade metindir. Herhangi bir HTML arındırma garantisi taşımazlar ve htmlContent istisnası değildirler. Metin olarak renderla.
Gömülebilir widget
Bir şey inşa etmek istemiyorsan bu iki satırı ekle. Widget, bir shadow root içine render eden bir custom element'tir, bu yüzden ne stillerini devralır ne de onlara sızar.
<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>
Her iki öznitelik de zorunludur. data-public-id akış kimliğindir, data-api widget'ın veri çektiği kaynaktır. İkisinden biri eksikse eleman konsola bir hata yazar ve hiçbir şey renderlamaz; orada bir şey görmen gerekirken boş bir alan görüyorsan kontrol edeceğin ilk şey budur.
Koyu görünüm için elemana data-theme="dark" ekle; sayfan bunu çalışma zamanında değiştirebilir. Daha derin bir stil için widget, kendi stil dosyanda ayarladığın CSS özel özelliklerini (--changelogapp-text, --changelogapp-bg, --changelogapp-accent ve daha fazlası) ve ::part() adlarını sunar. Uygulama her iki temayı Ayarlar, Herkese açık akış altında canlı olarak önizler.
Depolarınızın yalnızca bir kısmını göstermek için data-repos ekleyin; örneğin birden fazla ürün tek bir hesabı paylaşıyorsa, bir ürünün changelog'unu o ürünün sitesinde gösterebilirsiniz. Değer, virgülle ayrılmış owner/repo biçimindeki tam adların listesidir; sahibi olmayan bir ad hiçbir şeyle eşleşmez ve hata vermeden boş bir akış gösterir. En fazla on depo dikkate alınır. Bu şekilde daraltılmış bir widget yalnızca Updates ve Feedback sekmelerini gösterir, çünkü yol haritasının depo bazında bir görünümü yoktur; geri bildirim ise ekibinizin geri bildirim hedefinin gösterdiği yere kaydedilmeye devam eder. Ayarlar, Herkese açık akış altında bu özniteliği sizin için yazan bir seçici vardır.
Şu sırayla üç sekme render eder: Updates, Roadmap ve Feedback. İlk ikisi yukarıdaki akışları okur. Üçüncüsü aşağıdaki endpoint'e gönderir ve her gönderim kimliğini localStorage'da tutar, böylece bir ziyaretçi geri gelip gönderdiğine ne olduğunu görebilir.
Betik sürümlenmiş olarak sunulur. /widget.js her zaman en yeni build'i sunar ve bir saat önbelleğe alınır, böylece bir sürüm sana dokunmadan ziyaretçilerine ulaşır. /widget-vN.js bir build'i sabitler: bir sürüm numarası bir kez sunulduktan sonra byte'ları asla değişmez ve bir yıl önbelleğe alınır. Değişiklikleri kasıtlı olarak benimsemek istersen sabitle.
Sayfa başına tam olarak bir widget betiği yükle
İki URL alternatiftir, katman değildir. İkisi de aynı custom element adını kaydeder ve bir tarayıcı bir adın belge başına yalnızca bir kez kaydedilmesine izin verir: hangi betik önce çalışırsa sayfanın ömrü boyunca o kazanır ve ikincisi devre dışı kalır. Yani hem /widget.js hem de /widget-v5.js taşıyan bir sayfa, tarayıcının hangisini ilk çalıştırdığına bağlı olarak renderlar, bu senin kontrolünde olan bir şey değildir ve mevcut bir /widget.js'in yanına sürümü sabitlemek için /widget-v5.js eklemek hiçbir şey yapmaz.
Bu olduğunda widget her iki build'i de adlandıran bir uyarıyı konsola yazar, böylece tahmin etmek zorunda kalmazsın. Uyarmaktan fazlasını yapamaz: ikinci kopya çalıştığında ilki adı zaten talep etmiştir. Çözüm her zaman betik etiketini bir başkasını eklemek yerine değiştirmektir, ve bunu senin için bir etiket yöneticisi veya bir parça eklese bile aynı şey geçerlidir. Devam eden build'den sabitlenmiş bir tanesine geçmek için src'yi değiştir.
Barındırılan akış sayfası
https://feed.changeloop.dev/feed/YOUR_PUBLIC_IDO adreste sade bir sayfayı da biz barındırıyoruz: changelog'un ve roadmap panon, yukarıdaki aynı iki akıştan renderlanmış olarak. Giriş yapmana veya kendi tarafında bir şey kurmana gerek yoktur. Bir döngü kapandığında insanları geri gönderdiğimiz yer de burasıdır: bir GitHub issue'suna bıraktığımız Shipped yorumu buraya bağlanır, yukarıdaki gönderim sorgusundan gelen shippedEntry.link de öyle, ikisi de sonradan daha ileri bir sayfaya taşınsa bile kaydı yine de bulan kendi #entry-ID çapasıyla teslim edilen kayda iner.
Bunu bir yedek olarak ele al, entegrasyon olarak değil. Bunu kendi sitene, bizim değil kendi ürününmüş gibi görünecek şekilde koymanın yolu hâlâ changelog akışı ve widget'tır; bu sayfa henüz onu yapmadığın zamanlar ve başka ne inşa etmiş olursan ol buraya işaret eden döngü kapanış bağlantıları içindir.
Kendi alan adın
Barındırılan sayfayı DNS veya sertifika değişikliği olmadan kendi adresinden sunabilirsin. Ayarlar, Özel alan adı altında okuyucularının göreceği herkese açık adresi yapıştır (örneğin https://example.com/changelog), sonra sitendeki o yolu orada gösterilen proxy hedefine yönlendir: tek bir kural sayfayı, varlıklarını, verilerini ve akışlarını kapsar. Alan adımı kontrol et adresini bizim tarafımızdan çeker ve proxy'nin doğru olup olmadığını, değilse neyi değiştirmen gerektiğini söyler.
MCP sunucusu
POSThttps://api.changeloop.dev/mcpClaude Code, ChatGPT veya Model Context Protocol konuşan başka bir ajanla çalışıyorsan onu doğrudan changelog'una bağlayabilirsin. Ajan böylece incelemeyi bekleyeni görebilir, metni düzenleyebilir ve editörden çıkmadan yayımlayabilir. Bu, web uygulamasıyla aynı inceleme kapısıdır: bir şey onaylamadan hiçbir şey herkese açık olmaz.
Claude Code'u bağlamak
Önce bir API anahtarı oluştur (Ayarlar, API anahtarları), sonra sunucuyu başlıkta anahtarınla ekle:
claude mcp add --transport http changeloop \
https://api.changeloop.dev/mcp \
--header "Authorization: Bearer clapi_YOUR_KEY"
Bunun yerine bir JSON yapılandırması okuyan bir istemci için aynı şey şöyle görünür:
{
"mcpServers": {
"changeloop": {
"type": "http",
"url": "https://api.changeloop.dev/mcp",
"headers": { "Authorization": "Bearer clapi_YOUR_KEY" }
}
}
}
Henüz bir OAuth akışı yok. Kimlik doğrulama, yukarıdaki iki komutun yaptığı gibi başlıktaki API anahtarıdır. Ayarlar'da o anahtarı iptal etmek, ajanı bir sonraki isteğinde bağlantısını keser.
Ajanın yapabilecekleri
Yedi araç, ve liste kasıtlı olarak kısa. Bu ürünün yapabileceği başka her şeye aynı anahtarla REST API üzerinden ulaşılabilir; bir ajana açılan her araç, onu yapmaya ikna edilebileceği bir şey daha demektir.
- list_pending_entries, list_published_entries, get_entry - kayıtlarını okur. Bekleyenler herkese açık değildir.
- update_entry - bir kaydın başlığını veya markdown gövdesini değiştirir. Akışın sunduğu HTML, markdown'ından arındırıcımız tarafından yeniden renderlanır; bir ajan HTML sağlayamaz.
- approve_entry - yayımla. Bu herkese açık ve anlıktır ve GitHub'daki bağlı geri bildirimi bilgilendirir. Yalnızca bekleyen bir kayıt onaylanabilir.
- discard_entry - bir kaydı changelog dışında tut. Web uygulamasından geri alınabilir.
- get_changelog_info - akış kimliğin ve changelog'unun sunulduğu adresler.
Yapamayacakları
Her araç, anahtarın ait olduğu ekiple kapsamlandırılmıştır ve hiçbiri argüman olarak bir ekip almaz, bu yüzden bir şey denese bile başka bir ekibe işaret edecek bir şey yoktur. Sunucu bir tarayıcı oturumunu kabul etmez, yalnızca bir anahtarı: bir isteğin kimlik bilgisini kasıtlı olarak eklemesi gerekir. Ve bir anahtar anahtarları yönetemez veya veri dışa aktarımını indiremez, bu yüzden bu şekilde bağlanan bir ajan kendine ikinci bir kimlik bilgisi basamaz veya verini tek bir çağrıda dışarı çekemez.
API anahtarları
Yukarıdakilerin hepsi anonimdir ve bir kimlik bilgisi gerektirmez. Kimlik doğrulamalı API -- ayarların, inceleme kutun -- farklı bir yüzeydir ve ya giriş yapılmış bir tarayıcı oturumunu ya da bir API anahtarını kabul eder. Anahtarlar betikler ve ajanlar içindir: klavyede bir kişi olmadan changelog'una ulaşması gereken her şey.
Authorization: Bearer clapi_YOUR_KEYUygulamada Ayarlar altında, API anahtarları sekmesinde bir tane oluştur. Anahtar, oluşturduğun anda bir kez gösterilir ve bir daha asla: yalnızca bir hash'ini saklıyoruz, bu yüzden onu sana ikinci kez gösterebilecek hiçbir ekran yoktur. Kaybedersen iptal et ve bir tane daha oluştur.
Bir anahtarın yapabildikleri ve yapamadıkları
Bir anahtar, oluşturulduğu tek ekiple kapsamlandırılmış olarak giriş yapmakla aynı erişimi taşır, iki kasıtlı istisnayla. API anahtarlarını yönetemez ve veri dışa aktarımını indiremez. İkisi de gerçek bir girişe ihtiyaç duyar, böylece sızan bir anahtar kendine yedek basamaz, seni kilitlemek için kullanacağın anahtarları iptal edemez ve ekip verini tek bir istekte dışarı çekemez.
İptal etmek
İptal etmek bir sonraki istekte yürürlüğe girer. İptal edilmiş bir anahtar tıpkı bilinmeyen bir tane gibi 401 yanıtı verir ve hâlâ geçerli bir oturum tutan bir tarayıcıdan bile 401 vermeye devam eder, çünkü bir Authorization başlığı taşıyan bir istek sessizce bir çerez isteği olarak yeniden denenmez. İptal edilen anahtar, iptal edildiği tarih ve en son kullanıldığı tarihle listede kalır, ki sızan bir anahtarın nereye ulaştığını çözerken istediğin şey budur.
Planlar ve sınırlar
Ücretsiz plan ayda 20 birleştirilmiş değişikliği yazıya döker ve günlük incelenen birleştirme, önceliklendirilen geri bildirim, taslağı hazırlanan yol haritası kartı ve alternatif sürüm sayısını her biri için 50 ile sınırlar; ekip planında sabit sınır yoktur. Ayarlar, Plan ve kullanım her bütçeyi ürünün kendi saydığı şekilde, her birinin sıfırlanacağı anla birlikte, herhangi bir şey reddedilmeden önce gösterir. Bir sınırın üzerinde gelen iş kaybolmaz, bekletilir: kota üstündeki bir kayıt gelen kutusunda bekler, reddedilen bir yol haritası taslağı pencere yenilendiğinde yeniden denenebilir.
GitLab ve Bitbucket
Bir GitLab projesi veya bir Bitbucket deposu, bir GitHub deposuyla aynı şekilde changelog'unu besleyebilir: Ayarlar, sonra GitLab ya da Ayarlar, sonra Bitbucket altında bağla, sana verdiğimiz webhook'u ekle (ya da bitbucket.org'da, Bitbucket sayfası bu düğmeyi sunuyorsa, bunu Connect with Bitbucket'a bırak) ve adını verdiğin dala birleştirilen her değişiklik, aynı şekilde yazılan ve aynı insan incelemesiyle kapılanan bir taslak kayıt olarak inceleme kutuna düşer. Kayıtlar birleştirilen pull request'lerden veya merge request'lerden gelir; GitHub ve Bitbucket'ta Ayarlar, sonra What creates drafts altında push modunu seçersen push'lardan da gelir. GitLab projeleri yalnızca merge request'lerden taslak oluşturur.
Bir proje bağlamak
GitLab projeleri Ayarlar, sonra GitLab altında, Bitbucket depoları Ayarlar, sonra Bitbucket altında bağlanır. Yolu (GitLab'da acme/web gibi grup ve proje, Bitbucket'ta acme/app gibi çalışma alanı ve depo) gir, sana bir webhook adresi ve bir sır geri veririz. İkisini de kendi taraflarındaki webhook ayarlarına yapıştır: GitLab'da Merge request events'i işaretle, Bitbucket'ta Merged pull request ve Push repository tetikleyicilerini işaretle. Kendi sunucunda barındırılan örnekler https üzerinden çalışır. Sır, o anda bir kez gösterilir. Kaybedersen projeyi kaldır ve tekrar bağla. bitbucket.org'da, Bitbucket sayfasında Connect with Bitbucket düğmesi görünüyorsa, yapıştırma adımını atlayabilirsin: düğmeye bas, erişime bir kez izin ver, biz de deponun ana dalını okuyup webhook'u senin için ekleriz. Depoda yönetici yetkin olması gerekir. Kendi sunucunda barındırılan Bitbucket için ya da yapıştırmayı tercih ediyorsan Set it up by hand'i seç, adresi ve sırrı yukarıdaki gibi alırsın. Bir Bitbucket deposunu kaldırıp yeniden bağlarsan, Bitbucket'taki eski webhook'u da Repository settings, sonra Webhooks altından sil. Bir proje bağlandıktan sonra, satırından dalını değiştirebilir ve otomatik yayımlamayı açabilirsin; bir teslimat yok sayıldıysa satır nedenini söyler.
Bitbucket neden bir dal sorar ama GitLab sormaz
GitLab bize projenin varsayılan dal olarak neyi ele aldığını söyler, böylece alanı boş bırakıp bunu kastedebilirsin. Bitbucket hiç varsayılan dal göndermez, bu yüzden boş bırakmana izin versek karşılaştıracak hiçbir şeyimiz olmaz ve webhook'un mükemmel kurulmuş görünürken tek bir kayıt bile üretmeden orada dururdu. Bunun olmasına izin vermek yerine sana bir soru sormayı tercih ederiz. Connect with Bitbucket ile, erişime izin verdiğinde ana dalı Bitbucket'a biz sorarız, böylece onu yazman gerekmez.
Henüz kapsamadıkları
Changelog kayıtları, başka hiçbir şey değil. Senin için bir issue açan geri bildirim widget'ı, düzeltme yayımlandığında o issue'ya geri gönderilen yanıt, issue etiketleriyle yürütülen herkese açık roadmap ve inceleme kutusundaki kaynak önizlemesi bugün yalnızca GitHub'a özeldir.
Sebep, örtbas etmek yerine söylemeyi tercih ettiğimiz bir sebep. Bunların her biri, bizim tarafımızdan tutulan, projene yazma erişimi olan bir erişim jetonu gerektirir. Changelog kayıtları hiçbirini gerektirmez, çünkü yazıldıkları her şey webhook'un kendisiyle gelir, bu yüzden GitLab veya Bitbucket'ı webhook ile bağlamak bize hiçbir kimlik bilgisi vermez ve kodunun hiçbir okumasını sağlamaz. Connect with Bitbucket tek istisnadır. Bitbucket tek bir istek için bize depoyu ve pull request'lerini okuyabilen ve webhook'larını yönetebilen bir jeton ödünç verir, biz de onu yalnızca ana dalı okumak ve webhook'u eklemek için kullanır, sonra atarız. Hiçbir şey saklanmaz. Bir özellik listesini tamamlamak için bir jeton istemek yerine sana hiçbir şeye mal olmayan kısmı göndermeyi tercih ederiz.
Bir kaydın diğer sürümleri
Bir değişikliğin genellikle birden fazla kez açıklanması gerekir: changelog'da müşterilere, hakkında sorulara cevap veren kişiye ve kimsenin dört paragraf okumadığı bir kanalda. İnceleme kutusundan, onaylamadan önce bir kaydın iki ek sürümünden birini hazırlayabilirsin.
Bir duyuru sürümü bir veya iki satırdır ve kaydı onayladığında tam metin yerine Slack'e gönderilen şeydir. Bir destek notu dahili bir brifingdir: ne değişti, müşteriler neyi fark edecek ve bir temsilcinin neredeyse birebir söyleyebileceği bir cümle. İkisi de kullanılmadan önce yeniden yazabileceğin taslaklardır ve ikisi de kaldırılabilir.
Hiçbiri yayımlanmaz
Bu sürümler changelog sayfanda, herhangi bir akışta, widget'ta veya onları sunan API'de asla görünmez. Özellikle destek notu şirketin içindeki insanlar için yazılmıştır ve kaydın kendisinden daha doğrudan olabilir. Var olduğu tek yerler inceleme kutun ve, eğer kullanıyorsan, kendi kopyandır.
Neden yazıldıkları
Her zaman kayıttan, asla pull request'ten. Bu kasıtlıdır: kayıt zaten güvenlik düzeltmelerini belirsiz tutan kuraldan ve kendi incelemenden geçmiştir. Ondan yeniden yazılan bir sürüm kaldırdığın bir ayrıntıyı yeniden tanıtamaz, çünkü o ayrıntı modele verilenin içinde yoktur.
Slack'te duyurmak
Bir kaydı onayla, herkese açık olduğu anda bir Slack kanalına gönderilebilsin. Ayarlar altında, Slack sekmesinde bağla: kendi çalışma alanında gelen bir webhook oluştur, kanalı seç ve URL'yi yapıştır. Senin tarafında o webhook'un ötesinde hiçbir şey kurulmaz ve çalışma alanına erişim istemeyiz.
Mesaj, kayıt başlığını, onayladığın metni, kategorisini ve etiketlerini ve changelog'undaki kayda geri dönen bir bağlantıyı taşır. Markdown, Slack'in gerçekten renderladığı şeye çevrilir, böylece bir kayıt kendi yıldız işaretlerini gösterir şekilde gelmez.
Webhook URL'si bir kimlik bilgisidir
O URL'ye sahip olan herkes kanala gönderi yapabilir, bu yüzden onu bir şifre gibi ele alırız: saklanır ve ondan sonra kendi veri dışa aktarımın dahil hiçbir ekran ve hiçbir API yanıtı onu bir daha göstermez. Sonrasında gördüğün şey bir maskedir, iki webhook'u birbirinden ayırt etmek için yeterli ve başka kimse için işe yaramaz. Yalnızca bir hooks.slack.com adresini kabul ederiz, bu yüzden yanlış yazılmış veya değiştirilmiş bir URL getirilmek yerine reddedilir.
Çalışmayı bıraktığında
Slack'te uygulamayı kaldırırsan veya kanalı arşivlersen webhook kalıcı olarak çalışmayı bırakır. Bunu ilk reddedilen mesajda fark ederiz, duyuruları kapatırız ve sebebi ve tarihi ile birlikte Slack sekmesinde belirtiriz. Sessizce tekrar denemeye devam etmememiz kasıtlıdır: kimsenin duyurmadığı bir changelog kimsenin okumadığı bir changelog'la tıpatıp aynı görünür ve bu söylenmeye değer bir fark.
Duraklatmak
Duraklat, duyuruları durdurur ve webhook'u tutar, böylece devam ettirmek Slack'te başka bir turdan geçmek yerine tek bir tıktır. Bağlantıyı kes, URL'yi tamamen kaldırır. Her iki durumda da yayımlamanın kendisi etkilenmez: Slack, changelog'unun gönderi yaptığı bir kanaldır, asla beklediği bir kapı değildir. Bir şeyi onayladığında Slack ulaşılamazsa kayıt yine de yayımlanır ve duyuru kendiliğinden tekrar denenir.
RSS ve JSON Feed
GEThttps://api.changeloop.dev/v1/public/YOUR_PUBLIC_ID/rss.xmlGEThttps://api.changeloop.dev/v1/public/YOUR_PUBLIC_ID/feed.jsonYayımlanan aynı kayıtlar, okuyucuların anladığı iki formatta abone olunabilir bir akış olarak: RSS 2.0 ve JSON Feed 1.1. İkisi de changelog akışıyla aynı repos, category ve tag filtrelerini alır ve aynı Cache-Control ile ETag'i taşır. Hiçbiri sayfalanmaz: bir okuyucu akışın başını yoklar, bu yüzden bunlar cursor olmadan yalnızca en son kayıtları döndürür.
Kayıt metni, RSS için CDATA içine sarılmış, JSON Feed için content_html olarak arındırılmış HTML'dir. JSON Feed ayrıca ad alanlı bir _changelogapp uzantısı altında etiket renklerini taşır; RSS taşımaz, çünkü hiçbir okuyucu onları boyamaz.
Barındırılan sayfa ikisini de rel="alternate" bağlantıları olarak duyurur, böylece oraya inen bir tarayıcı veya okuyucu yollar söylenmeden abone olabilir.
Tek başına bir kayıt
GEThttps://api.changeloop.dev/v1/public/YOUR_PUBLIC_ID/entries/ENTRY_IDChangelog akışının data dizisinde taşıdığı aynı nesne olan tek bir yayımlanmış kaydı döndürür. Akışlardaki kalıcı bağlantıların işaret ettiği şey budur ve bir kimliğin olduğunda onu bulmak için akışı sayfalamak istemediğinde yararlıdır. Bilinmeyen bir kimlik veya yayımlanmamış bir kayda ait bir kimlik, diğer bilinmeyen bir kimlikle aynı gövdeyle 404 döndürür.
Markdown akışı
GEThttps://api.changeloop.dev/v1/public/YOUR_PUBLIC_ID/changelog.mdYayımlanan aynı kayıtlar, text/markdown olarak sunulan sade markdown hâlinde. Tarayıcı olmayan okuyucular içindir: "bu üründe son zamanlarda ne değişti" sorusuna cevap veren bir LLM veya bir ajan, RSS ayrıştırmadan veya JSON'da gezinmeden metni alır. Changelog akışıyla aynı repos, category ve tag filtrelerini alır, aynı Cache-Control ve ETag'i taşır ve diğer ikisi gibi koşullu bir isteğe 304 ile cevap verir.
Her kayıt bir bölümdür: başlık olarak title, sonra tarihi, kategoriyi ve varsa etiketleri taşıyan tek bir satır, sonra yazıldığı şekliyle kayıt metni, sonra kaydın varsa Learn more bağlantısı, sonra kalıcı bağlantısı. Belge, akış başlığın ve açıklamanla açılır ve barındırılan sayfaya geri bağlanır. Henüz hiçbir şey yayımlanmadığında boş bir gövde döndürmek yerine bunu bir cümlede söyler, böylece bir okuyucu bunu başarısız bir getirmeden ayırt edebilir.
Barındırılan sayfa bunu, RSS ve JSON Feed bağlantılarının yanında, type text/markdown ile bir rel="alternate" bağlantısı olarak duyurur, böylece HTML'i getirmiş bir ajan yol söylenmeden onu bulabilir.
Sunduğu şey, arındırılmış HTML değil, bizim hazırladığımız ve senin onayladığın markdown'dır. Bu, kendisi etkisiz olan markdown olarak güvenlidir ve bu yanıtın asla text/html olmamasının sebebi budur. Bunu kendin renderlıyorsan, herhangi bir başka güvenilmeyen markdown'ı kaçırdığın gibi kaçır: herkese açık bir depodan hazırlanan kayıtlar orada bir pull request açabilen herkes tarafından etkilenebilir.
Kendi sitenden geri bildirim toplamak
Bunu test etmeden önce kaynaklarını ekle
Bu, üründe yazma yapan tek endpoint'tir, bu yüzden her yerden gelen istekleri kabul etmez. Tarayıcının Origin başlığını ekip başına bir izin listesiyle eşleştirir ve o liste boş başlar. Boş, her şeyi kabul et değil her şeyi reddet demektir. Gömdüğün kaynağı ekleyene kadar her tek gönderim {"error":"origin_not_allowed"} ile 403 döner ve kutuna hiçbir şey ulaşmaz. Formun doğru görünüyor ama yine de başarısız oluyorsa, neredeyse her zaman sebep budur. Listeyi, {"allowedOrigins": ["https://your-site.example"]} taşıyan giriş yapılmış bir PATCH ile /v1/settings/feed'e ayarla ve aynı yola bir GET ile geri oku, ki bu publicId'nle, allowedOrigins'inle ve aboneliklerinin bir akış okuyucusunda gördüğü feedTitle ve feedDescription ile cevap verir. Her kaynağı bir tarayıcının gönderdiği tam biçimde saklarız, bu yüzden gönderdiğinde sondaki bir eğik çizgi veya açık bir varsayılan port sorun değildir.
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 bir e-posta adresine benzemeli ve 254 karakter veya daha az olmalıdır. message boş olmamalı ve karakter değil UTF-8 byte olarak ölçülen 2KB veya daha az olmalıdır. JSON gövdesinin tamamı 8KB ile sınırlıdır. Bir alan daha var, website: bu bir bal küpüdür, bu yüzden dışarıda bırak veya widget'ımızın yaptığı gibi gizli bir girdi olarak renderlıyorsan boş gönder.
Bal küpü, onunla herhangi bir şeyi hata ayıklamadan önce anlamaya değer. website içinde bir şeyle gelirse tamamen sıradan görünen bir gönderim kimliğiyle 202 cevabı veririz ve sonra hiçbir şey yapmayız, çünkü yakalandığını öğrenen bir bot farklı bir şekilde tekrar dener. Bu bir bot için doğru cevap ve senin için kafa karıştırıcı bir cevaptır, bu yüzden kendi formunda bir tarayıcının otomatik doldurabileceği website adında bir alan varsa yeniden adlandır veya kaldır. Kabul edilmiş gibi görünen ve asla görünmeyen bir gönderim neredeyse her zaman budur.
Kabul ettiğimiz bir gönderim, bir publicSubmissionId ile 202 döndürür. Bunu gönderene geri ver ve mümkünse sakla: sonrasında ne olduğunu sorgulamasının tek yolu budur.
Başarısızlık modları şunlardır: yanlış şekil için invalid_email veya invalid_message ile 400, doğru şekil ama fazla büyük olduğu için email_too_large veya message_too_large ile 413, bir adresten bir akışa dakikada 5 veya saatte 30 gönderimi aştığında rate_limited ile 429, origin_not_allowed ile 403 ve tanımadığımız bir akış kimliği için not_found ile 404.
Ayrıca ekip başına, gönderimlerin ne kadar sonraki işi tetikleyebileceğine dair günlük bir sınır vardır. Bunu aştığında gelen her şeyi yine de kabul edip saklarız, sadece kendi kendine bir şey açmak yerine ekibinden birinin bakmasını bekler.
Bir gönderimi kontrol etmek
GET/v1/public/YOUR_PUBLIC_ID/feedback/PUBLIC_SUBMISSION_IDstatus ile cevap verir, artı o gönderim için bir issue varsa githubIssueUrl, artı iş çıktığında bir başlık ve bir bağlantı taşıyan shippedEntry. Gönderenin e-posta adresi bu rota için veritabanımızdan asla okunmaz, döndürülmesi bir yana; bu da yanıtı herkesin görebileceği bir sayfada renderlamayı güvenli kılan şeydir. Kimlik kimlik bilgisinin tamamıdır, buna göre davran. Adres ve akış başına dakikada 20 istek ve saatte 200 istekle sınırlıdır.
Önbelleğe alma, CORS ve koşullu istekler
Her iki akış da güçlü bir ETag ile birlikte Cache-Control: public, max-age=60, stale-while-revalidate=300 gönderir. O ETag'i If-None-Match olarak geri gönder, değişmemiş bir akış gövdesiz 304 cevabı verir. Hiçbir yanıt alanı bir duvar saati değeri taşımaz, bu yüzden değişmemiş veriyi yeniden renderladığımızda ETag sabit kalır, ki bu 304'lere güvenmeyi değerli kılan şeydir.
İki akış ve gönderim sorgusu anonim okumalardır ve Access-Control-Allow-Origin: * ile cevap verir, böylece onları herhangi bir kaynaktan, curl'den veya bir build adımından çağırabilirsin. Geri bildirim POST'u istisnadır: kendi izin verilen kaynağınla ve bir Vary: Origin ile cevap verir, asla bir joker karakterle değil. Tarayıcılar bunu ön uçuşa tabi tutar ve bir ön uçuş, kaynak izinli olsun olmasın her zaman 204 cevabı verir, bu yüzden ayarlarını sorgulamak için kullanılamaz.