Format a Keep a Changelog entry from grouped changes
Turn structured release information into a consistent Keep a Changelog entry without manually arranging headings and bullets.
Run — free
Provide a version, a release date, and lists for added, changed, fixed, or removed work. The formatter preserves the order of your descriptions, omits empty sections, and returns Markdown ready to place in a changelog. Its deterministic output is useful in release scripts, continuous delivery workflows, repository tools, and editorial checks where exactly the same input must always produce exactly the same text.
Prepare release information before formatting
Begin with a release version and date, then separate every noteworthy change into one of the four supported groups: added, changed, fixed, or removed. Added is appropriate for new functionality that users can now access. Changed describes meaningful adjustments to existing behavior, interfaces, defaults, or performance. Fixed records corrected defects, while removed identifies functionality that is no longer available. Write each list item as a complete, concise description that makes sense without internal ticket context. The formatter deliberately does not invent, summarize, or edit release notes, so the wording you provide remains the wording readers receive. Item order is also preserved within every group, allowing you to put important changes first. A group may be omitted or supplied as an empty array when the release contains no change of that type. The version cannot be empty, because an unversioned release heading would be ambiguous and unsuitable for a durable changelog. The date must likewise be supplied as meaningful text; using the familiar YYYY-MM-DD form keeps entries easy to scan and sort across regions.
Understand the generated Markdown structure
The result starts with a second-level Markdown heading containing the version in square brackets, followed by a hyphen and the supplied date. Non-empty groups then appear in the conventional order Added, Changed, Fixed, and Removed, each as a third-level heading with a bulleted list below it. This order stays stable even when the properties in an input object arrive in a different order. Empty groups do not produce headings, avoiding blank sections that distract readers and suggest missing information. Every description is trimmed at its boundaries. If a description already begins with a common Markdown bullet marker, the formatter removes that marker before adding the standard dash, which prevents accidental doubled bullets while preserving the description itself. The returned object contains the complete entry in its changelog field and ends the Markdown with a newline, making it straightforward to concatenate with other text or write through a release automation step. No current time, locale, random value, network response, or hidden state influences the output, so identical valid inputs produce identical text in the browser and through the API.
Place the formatter in a release workflow
Use the formatter after your team has decided what belongs in the release notes and before the entry is inserted into the repository changelog. A build script can collect approved descriptions from structured configuration, call the formatter, and either present the result for review or pass it to a separate file-update step. Keeping formatting separate from discovery is valuable: reviewers can assess whether every change is accurate without also debating inconsistent Markdown, while automation can rely on a predictable shape. The capability does not read commits, query issue trackers, modify files, create version links, or choose semantic versions. Those concerns remain with your existing release process, and this focused boundary makes the result easier to test. Validate the generated entry in the context of the full changelog if your project uses custom headings or link definitions. For public releases, prefer descriptions that explain user-visible impact rather than implementation details. For internal tools, retain enough precision to help operators understand migrations, compatibility changes, and removed behavior. Each request costs $0.002 through the API, while the same deterministic logic supports the browser experience.
What you can do with it
Standardize release automation
Convert structured release data into predictable Markdown before a deployment workflow updates the project changelog.
Prepare notes for review
Give maintainers a consistently ordered entry so review can focus on accuracy, clarity, and release impact.
Unify multiple repositories
Apply the same headings and bullet conventions across services that produce their release data in a shared structure.
FAQ
What does it cost?
Each API request costs $0.002.
Which change types are supported?
The formatter supports added, changed, fixed, and removed groups, in that output order.
What happens to an empty group?
Its heading is omitted, so the entry contains only sections with at least one change.
Does it generate release notes from commits?
No. It formats descriptions you already provide and does not inspect commits, issues, pull requests, or repositories.
Does the version need to follow semantic versioning?
No. The version must be a non-empty string, but the formatter preserves the versioning scheme you use.
Does it modify my changelog file?
No. It returns a Markdown entry; inserting that entry into a file is a separate step in your workflow.
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, by email and from Telegram — and soon from our app too.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/dev2/changelog-entry-format \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"version":"2.4.0","date":"2026-07-25"}'const res = await fetch("https://api.kit.forhosting.com/dev2/changelog-entry-format", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"version": "2.4.0",
"date": "2026-07-25"
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/dev2/changelog-entry-format",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"version": "2.4.0",
"date": "2026-07-25"
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/dev2/changelog-entry-format", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"version":"2.4.0","date":"2026-07-25"}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"version":"2.4.0","date":"2026-07-25"}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/dev2/changelog-entry-format", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"version": "2.4.0",
"date": "2026-07-25"
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "dev2.changelog_entry_format",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |