Conventional Commits message linter and parser
This Conventional Commits message linter checks whether a commit header follows the familiar type, optional scope, and description structure, then returns those parts as stable structured data.
Run — free
It recognizes the common build, chore, CI, documentation, feature, fix, performance, refactor, revert, style, and test types. Use it to catch malformed or unknown headers before they enter a shared history, release workflow, or automated changelog. The compact result is ready for scripts without additional text parsing.
Check the header and extract useful fields
Send the complete commit message in the text field. The linter normalizes line endings and examines the first line as the Conventional Commits header, so a message may still contain a blank line, body paragraphs, and footers below it. A valid header begins with a recognized lowercase type. It may continue with a scope in parentheses, may add an exclamation mark to flag a breaking change, and must then contain a colon, exactly one separating space, and a non-empty description. For example, <code>feat(parser): support escaped delimiters</code> yields the type <code>feat</code>, the scope <code>parser</code>, and the description <code>support escaped delimiters</code>. The normal result also includes <code>valid: true</code> and a Boolean breaking field. When no scope is present, the scope property is omitted instead of being filled with a misleading empty or null value. Syntax failures and unknown types produce an invalid-input error with a direct explanation, allowing an editor hook or pipeline to show a useful correction rather than interpreting a partly parsed result.
Understand the recognized convention
Conventional Commits defines the shape of a header while leaving projects room to establish their own type vocabulary. This capability deliberately uses a fixed, practical set so results are predictable across repositories: build, chore, ci, docs, feat, fix, perf, refactor, revert, style, and test. A correctly shaped header with another word still fails because accepting every word would not provide the requested type validation. Types must be lowercase. Scopes are optional and may contain lowercase letters, digits, dots, underscores, slashes, or hyphens; they must start with a letter or digit. This makes common scopes such as <code>api</code>, <code>web-client</code>, and <code>packages/core</code> usable while rejecting ambiguous whitespace and unmatched parentheses. The description preserves its original punctuation and capitalization, but it may not start or end with whitespace. An exclamation mark immediately before the colon indicates a breaking change and is returned separately. A <code>BREAKING CHANGE</code> footer may remain in the body, but the current result derives its breaking Boolean from the header marker only; it does not interpret footer semantics.
Place deterministic validation in development workflows
Use the linter at the earliest point where a proposed message is available: a commit-message editor hook, a pull-request check, a merge queue, or a service that prepares release metadata. A local hook gives the quickest feedback, while server-side validation ensures that commits created by automation or alternate clients follow the same policy. The parser is deterministic and performs a bounded string scan. It does not call a repository host, inspect a Git object, infer intent from a diff, rewrite the supplied description, or contact any external service. The same input therefore produces the same fields or the same error in a browser and through the API. Treat the returned type, scope, and description as classification data that can feed changelog grouping, release rules, or dashboards, but keep project-specific semantic checks separate. For example, this linter can confirm that <code>fix(auth): reject expired tokens</code> is structurally valid; it cannot prove that the change fixes a bug or that <code>auth</code> is an allowed package in your repository. Combine it with repository policy when stricter scope lists or ticket references are required.
What you can do with it
Guard a commit-msg hook
Reject malformed headers immediately and show authors the exact Conventional Commits structure expected by the repository.
Validate merge automation
Check messages created by squash, merge-queue, or release tooling before they become part of the permanent history.
Classify release inputs
Extract stable type, scope, description, and breaking status for changelog grouping or release-policy decisions.
FAQ
How much does one validation cost?
Each API request costs $0.002. The browser version can also run directly on this page.
Which commit types are recognized?
The recognized types are build, chore, ci, docs, feat, fix, perf, refactor, revert, style, and test.
Is a scope required?
No. Both feat: add export and feat(api): add export are valid. When absent, scope is omitted from the result.
Can the message include a body and footers?
Yes. The first line is validated as the header, while subsequent body and footer lines are preserved as input but are not parsed into output fields.
Does an exclamation mark identify a breaking change?
Yes. An exclamation mark immediately before the colon sets breaking to true. Footer-only breaking-change declarations are not interpreted.
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/commit-message-lint \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"text":"feat(parser): support escaped delimiters"}'const res = await fetch("https://api.kit.forhosting.com/dev2/commit-message-lint", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"text": "feat(parser): support escaped delimiters"
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/dev2/commit-message-lint",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"text": "feat(parser): support escaped delimiters"
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/dev2/commit-message-lint", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"text":"feat(parser): support escaped delimiters"}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"text":"feat(parser): support escaped delimiters"}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/dev2/commit-message-lint", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"text": "feat(parser): support escaped delimiters"
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "dev2.commit_message_lint",
"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.
Limits
max_chars | 100000 |
max_header_chars | 1000 |
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. |