Podcast chapter timestamp formatter
Turn a list of podcast chapter titles and start times into consistent, copy-ready chapter markers.
Run — free
Enter every start time as a whole number of seconds, and the formatter converts it to HH:MM:SS followed by the chapter title. It also checks that each chapter begins later than the one before it, catching duplicated or misplaced times before they reach your show notes, hosting dashboard, production handoff, or publishing workflow.
Prepare chapter titles and start times
Create one row for every chapter in the same order that listeners encounter it. Each row needs a descriptive title and a non-negative whole-number start time measured in seconds from the beginning of the episode. For example, the opening can start at 0, a conversation can start at 95, and a later segment can start at 3725. You do not need to convert those values into hours, minutes, and seconds yourself. Keep each title on one line so every output marker remains a single, portable line. Leading and trailing spaces around a title are removed, while the wording and capitalization inside it are preserved. The formatter accepts up to 500 rows, which is ample for detailed long-form programs while still placing a clear bound on input size. Review the source timing against the final exported audio rather than an earlier edit, because an inserted advertisement, trimmed introduction, or replaced music bed can shift every chapter that follows. Accurate source seconds are the foundation of useful navigation markers.
Understand formatting and order validation
Every accepted start time is split deterministically into hours, minutes, and seconds. Each component receives at least two digits, producing markers such as 00:00:00 - Welcome and 01:02:05 - Listener questions. Hours are not wrapped after 24, so unusually long recordings remain accurate instead of looking like a new day began. The formatter then joins the lines in the exact order supplied. Before returning anything, it verifies that each start time is strictly greater than the preceding value. Equal times are rejected because two chapters cannot establish a clear sequential boundary at the same second, and a lower time is rejected because it would place a later row earlier in playback. The error identifies the row and the conflicting seconds, making the source list easier to repair. Start times must also be finite, safe, whole numbers within the published range. These rules prevent silent rounding, ambiguous ordering, and malformed timestamps from entering an otherwise polished episode description.
Publish and automate the markers
Copy the returned markers into any destination that accepts the conventional HH:MM:SS - Title layout, including production notes, episode descriptions, content-management fields, and handoff documents. Some podcast hosts or listening platforms may apply their own requirements, such as insisting that the first chapter begin at zero or recognizing a slightly different separator. This formatter deliberately does not invent those service-specific rules: it produces a clean, predictable baseline and validates the ordering that every chapter list needs. Check your destination's current publishing guidance before release, especially if automatic clickable chapters are important. In an automated workflow, send the chapter array after an editor or transcription process approves the final timing, then store or publish the returned markers string without another formatting pass. The browser version runs the same deterministic solver as the API, so a list checked manually will produce identical text when automated. Browser use is free, while each API request uses the displayed $0.002 base price.
What you can do with it
Prepare an episode description
Convert an editor-approved chapter sheet into consistent markers that can be pasted into show notes or a publishing form.
Validate a production handoff
Catch duplicated or out-of-order start times before a producer sends the final chapter list to a host or client.
Standardize an automated workflow
Give every episode the same timestamp width and separator without relying on manual conversion from raw seconds.
FAQ
What does it cost?
It is free to run in your browser on this page. An API request costs $0.002.
What format does it produce?
Each line uses HH:MM:SS - Title, with hours, minutes, and seconds padded to at least two digits.
Can two chapters have the same start time?
No. Start times must be strictly increasing, so every chapter must begin later than the previous chapter.
Must the first chapter start at zero?
No. The formatter accepts any non-negative first start time, although a particular podcast host may impose its own publishing rule.
Can I enter decimal seconds?
No. Start times must be whole seconds, avoiding hidden rounding and keeping the output unambiguous.
Does it change my chapter titles?
It removes surrounding whitespace but otherwise preserves the title's wording and capitalization. Titles containing line breaks are rejected.
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/audio/podcast-chapter-timestamps \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"chapters":[{"title":"Welcome","start_time":0},{"title":"The main interview","start_time":95},{"title":"Listener questions","start_time":3725}]}'const res = await fetch("https://api.kit.forhosting.com/audio/podcast-chapter-timestamps", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"chapters": [
{
"title": "Welcome",
"start_time": 0
},
{
"title": "The main interview",
"start_time": 95
},
{
"title": "Listener questions",
"start_time": 3725
}
]
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/audio/podcast-chapter-timestamps",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"chapters": [
{
"title": "Welcome",
"start_time": 0
},
{
"title": "The main interview",
"start_time": 95
},
{
"title": "Listener questions",
"start_time": 3725
}
]
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/audio/podcast-chapter-timestamps", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"chapters":[{"title":"Welcome","start_time":0},{"title":"The main interview","start_time":95},{"title":"Listener questions","start_time":3725}]}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"chapters":[{"title":"Welcome","start_time":0},{"title":"The main interview","start_time":95},{"title":"Listener questions","start_time":3725}]}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/audio/podcast-chapter-timestamps", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"chapters": [
{
"title": "Welcome",
"start_time": 0
},
{
"title": "The main interview",
"start_time": 95
},
{
"title": "Listener questions",
"start_time": 3725
}
]
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "audio.podcast_chapter_timestamps",
"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_chapters | 500 |
max_start_time | 359999999 |
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. |