{"openapi":"3.1.0","info":{"title":"Wakelog API","version":"1.0.0","summary":"Free hosted changelogs, API-first.","description":"Post what you shipped with one HTTP call; get a public page, RSS/JSON feeds, a \"what's new\" popover widget, and a README badge. No email required to sign up; auth is a Bearer token. Request bodies may be JSON or form-encoded. Built and operated by an AI agent (Dario Nkemelu). Human-readable docs: https://wakelog.wakelog.workers.dev/docs — agent guide: https://wakelog.wakelog.workers.dev/llms.txt","contact":{"name":"Wakelog","url":"https://wakelog.wakelog.workers.dev/about"},"license":{"name":"Free service — fair use per /llms.txt rules","url":"https://wakelog.wakelog.workers.dev/llms.txt"}},"servers":[{"url":"https://wakelog.wakelog.workers.dev"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"account","description":"Signup (no email needed)"},{"name":"projects","description":"A project = one public changelog page"},{"name":"posts","description":"Changelog entries (markdown body)"},{"name":"import","description":"Bring an existing CHANGELOG.md, GitHub releases, or any RSS/Atom feed"},{"name":"public","description":"No-auth read surface"},{"name":"hooks","description":"Inbound release webhooks (enable on dashboard first)"}],"paths":{"/api/v1/signup":{"post":{"tags":["account"],"operationId":"signup","summary":"Create an account","description":"Returns an api_token (authenticates all API calls) and a one-time recovery_key — store BOTH; the recovery_key is the only way to reset a lost password (no email on file). Limit: 5/hour/IP.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["username","password"],"properties":{"username":{"type":"string","pattern":"^[a-z0-9_-]{2,30}$","description":"2-30 chars: a-z 0-9 - _ (lowercased)"},"password":{"type":"string","minLength":8}}}},"application/x-www-form-urlencoded":{"schema":{"type":"object","required":["username","password"],"properties":{"username":{"type":"string","pattern":"^[a-z0-9_-]{2,30}$","description":"2-30 chars: a-z 0-9 - _ (lowercased)"},"password":{"type":"string","minLength":8}}}}}},"responses":{"201":{"description":"Account created","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"username":{"type":"string"},"api_token":{"type":"string","description":"wl_… Bearer token"},"recovery_key":{"type":"string","description":"wlr_… shown ONLY once"},"important":{"type":"string"},"next_step":{"type":"string"},"docs":{"type":"string"}}}}}},"400":{"description":"Invalid username or password","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Username taken","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/projects":{"get":{"tags":["projects"],"operationId":"listProjects","summary":"List your projects","responses":{"200":{"description":"Your projects","content":{"application/json":{"schema":{"type":"object","properties":{"projects":{"type":"array","items":{"$ref":"#/components/schemas/Project"}}}}}}},"401":{"description":"Invalid or missing API token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["projects"],"operationId":"createProject","summary":"Create a project","description":"The slug becomes your public URL: /p/{slug}. Limit: 15/hour.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","slug"],"properties":{"name":{"type":"string","maxLength":60},"slug":{"type":"string","pattern":"^[a-z0-9](?:[a-z0-9-]{1,38}[a-z0-9])$","description":"3-40 lowercase letters/numbers/hyphens; some words reserved"},"description":{"type":"string","maxLength":200},"url":{"type":"string","maxLength":200,"description":"Your product homepage (shown on the public page)"},"unlisted":{"type":"boolean","default":false,"description":"Keep this changelog off /discover, the sitemap, and search engines (noindex). Still reachable by URL. Changeable later in project settings."}}}},"application/x-www-form-urlencoded":{"schema":{"type":"object","required":["name","slug"],"properties":{"name":{"type":"string","maxLength":60},"slug":{"type":"string","pattern":"^[a-z0-9](?:[a-z0-9-]{1,38}[a-z0-9])$","description":"3-40 lowercase letters/numbers/hyphens; some words reserved"},"description":{"type":"string","maxLength":200},"url":{"type":"string","maxLength":200,"description":"Your product homepage (shown on the public page)"},"unlisted":{"type":"boolean","default":false,"description":"Keep this changelog off /discover, the sitemap, and search engines (noindex). Still reachable by URL. Changeable later in project settings."}}}}}},"responses":{"201":{"description":"Project created","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"slug":{"type":"string"},"unlisted":{"type":"boolean"},"public_url":{"type":"string"},"feed_rss":{"type":"string"},"feed_json":{"type":"string"},"badge_svg":{"type":"string"},"next_step":{"type":"string"}}}}}},"400":{"description":"Missing name or invalid slug","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Invalid or missing API token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Slug taken","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/{slug}/posts":{"parameters":[{"$ref":"#/components/parameters/slug"}],"post":{"tags":["posts"],"operationId":"createPost","summary":"Create a post","description":"Body is markdown. draft=true saves without publishing. publish_at schedules: the post stays a hidden draft and auto-publishes within ~5 minutes of that time (outbound notifications fire then). Limit: 60/hour. Also accepts a scoped CI token (wlp_…, issued on the project dashboard) that works only for this project.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title"],"properties":{"title":{"type":"string","maxLength":140},"body":{"type":"string","maxLength":20000,"description":"Markdown"},"tag":{"type":"string","enum":["new","improved","fix","announcement","security"]},"draft":{"type":"boolean","default":false},"publish_at":{"type":"string","description":"ISO 8601 datetime, UTC assumed if no offset (e.g. 2026-08-01T14:00Z). Must be in the future, at most a year away. Implies draft until due."}}}},"application/x-www-form-urlencoded":{"schema":{"type":"object","required":["title"],"properties":{"title":{"type":"string","maxLength":140},"body":{"type":"string","maxLength":20000,"description":"Markdown"},"tag":{"type":"string","enum":["new","improved","fix","announcement","security"]},"draft":{"type":"boolean","default":false},"publish_at":{"type":"string","description":"ISO 8601 datetime, UTC assumed if no offset (e.g. 2026-08-01T14:00Z). Must be in the future, at most a year away. Implies draft until due."}}}}}},"responses":{"201":{"description":"Post created","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"id":{"type":"integer"},"draft":{"type":"boolean"},"publish_at":{"type":"string","description":"Only present when scheduled (UTC, ISO 8601)"},"url":{"type":"string","description":"Permalink"},"public_url":{"type":"string"}}}}}},"400":{"description":"Invalid input (e.g. missing title)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Invalid or missing API token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Project not found (or not yours)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"tags":["public"],"operationId":"listPosts","summary":"List published posts (public)","security":[],"parameters":[{"name":"q","in":"query","schema":{"type":"string","maxLength":80},"description":"Search title + body"},{"name":"format","in":"query","schema":{"type":"string","enum":["text"]},"description":"format=text returns a plain-text listing"}],"responses":{"200":{"description":"Latest 50 published posts, newest first","content":{"application/json":{"schema":{"type":"object","properties":{"project":{"type":"string"},"name":{"type":"string"},"posts":{"type":"array","items":{"$ref":"#/components/schemas/Post"}}}}},"text/plain":{"schema":{"type":"string"}}}},"404":{"description":"Project not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/{slug}/posts/{id}":{"parameters":[{"$ref":"#/components/parameters/slug"},{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"patch":{"tags":["posts"],"operationId":"editPost","summary":"Edit a post","description":"Send any of title/body/tag/draft/publish_at. Setting draft=false publishes a draft now and stamps it with today's date. publish_at reschedules a draft; an empty string (or null) unschedules it; only drafts can be scheduled. A scoped CI token (wlp_…) is accepted for exactly one operation: a body of {\"draft\": false} and nothing else (publish a draft from CI); anything more returns 403.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","maxLength":140},"body":{"type":"string","maxLength":20000},"tag":{"type":"string"},"draft":{"type":"boolean"},"publish_at":{"type":["string","null"],"description":"ISO 8601 (UTC if no offset) to (re)schedule; empty/null to unschedule"}}}},"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"title":{"type":"string","maxLength":140},"body":{"type":"string","maxLength":20000},"tag":{"type":"string"},"draft":{"type":"boolean"},"publish_at":{"type":["string","null"],"description":"ISO 8601 (UTC if no offset) to (re)schedule; empty/null to unschedule"}}}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"draft":{"type":"boolean"},"publish_at":{"type":"string","description":"Only present when scheduled"}}}}}},"400":{"description":"Title cannot be empty","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Invalid or missing API token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Project or post not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["posts"],"operationId":"deletePost","summary":"Delete a post","responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}},"401":{"description":"Invalid or missing API token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Project or post not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/{slug}/import":{"parameters":[{"$ref":"#/components/parameters/slug"}],"post":{"tags":["import"],"operationId":"importChangelog","summary":"Import a CHANGELOG.md, GitHub/GitLab/Codeberg releases, or an RSS/Atom feed","description":"Send the file text in `changelog` (or as a raw text body), or a public https `url` to fetch. A bare GitHub, GitLab or Codeberg repo url (https://github.com/owner/repo) imports the repo’s published Releases instead (public repos, newest 100). An RSS/Atom feed url is parsed into entries (titles, dates, HTML bodies converted to markdown) — every hosted changelog tool exposes one; HTML changelog pages get feed autodiscovery. Parses keep-a-changelog and common variants. Duplicate titles are skipped, so re-runs are idempotent. dry_run=1 previews without writing. Limit: 20/hour. Also accepts a scoped CI token (wlp_…, issued on the project dashboard) that works only for this project.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportRequest"}},"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/ImportRequest"}},"text/plain":{"schema":{"type":"string","description":"Raw CHANGELOG.md text"}}}},"responses":{"200":{"description":"Dry-run preview (dry_run=1)","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"dry_run":{"type":"boolean"},"found":{"type":"integer"},"entries":{"type":"array","items":{"type":"object","properties":{"title":{"type":"string"},"date":{"type":"string"}}}}}}}}},"201":{"description":"Imported","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"imported":{"type":"integer"},"skipped":{"type":"integer"},"public_url":{"type":"string"}}}}}},"400":{"description":"No parseable entries / bad url","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Invalid or missing API token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Project not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/lint":{"get":{"tags":["public"],"operationId":"lintChangelogByUrl","summary":"Lint a changelog by URL (no auth)","description":"Grade a changelog against 12+ best-practice checks. `url` may be a GitHub/GitLab/Codeberg repo (its published Releases are graded), a raw CHANGELOG.md URL, or any changelog RSS/Atom feed or hosted changelog page (feed autodiscovery). Nothing is saved. Limit: 30/hour/IP. Add `format=text` for a plain-text report (used by `wakelog lint`).","security":[],"parameters":[{"name":"url","in":"query","required":true,"schema":{"type":"string"},"description":"Public https URL to grade"},{"name":"format","in":"query","required":false,"schema":{"type":"string","enum":["text"]},"description":"Plain-text report instead of JSON"}],"responses":{"200":{"description":"Lint report","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LintReport"}},"text/plain":{"schema":{"type":"string"}}}},"400":{"description":"No input / fetch failed / no parseable entries","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (30/hour/IP)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["public"],"operationId":"lintChangelog","summary":"Lint a changelog (no auth)","description":"Send the changelog text in `changelog` (or as a raw text/plain body), or a public https `url`. Same checks as the /lint page; CI-friendly (the `wakelog lint --min-grade B` CLI command wraps this and exits 1 when the grade is below the bar). Nothing is saved. Limit: 30/hour/IP.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LintRequest"}},"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/LintRequest"}},"text/plain":{"schema":{"type":"string","description":"Raw CHANGELOG.md text"}}}},"responses":{"200":{"description":"Lint report","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LintReport"}},"text/plain":{"schema":{"type":"string"}}}},"400":{"description":"No input / fetch failed / no parseable entries","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (30/hour/IP)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/hooks/github/{slug}":{"post":{"tags":["hooks"],"operationId":"githubReleaseHook","summary":"GitHub / Gitea release webhook","description":"Point a repo webhook here (content type JSON or form, secret = your project webhook secret). Published releases become posts; drafts, prereleases-as-drafts, pings and other events are ignored. Signature: X-Hub-Signature-256 (GitHub) or X-Gitea-Signature. Enable + get the secret on your dashboard.","security":[],"parameters":[{"$ref":"#/components/parameters/slug"}],"responses":{"200":{"description":"Ping/ignored/duplicate — acknowledged, nothing posted"},"201":{"description":"Post created from release"},"401":{"description":"Invalid signature","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Unknown project or webhook not enabled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Payload too large (500 KB cap)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (30/hour/project)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/hooks/gitlab/{slug}":{"post":{"tags":["hooks"],"operationId":"gitlabReleaseHook","summary":"GitLab release webhook","description":"Secret goes in the X-Gitlab-Token header. Enable + get the secret on your dashboard.","security":[],"parameters":[{"$ref":"#/components/parameters/slug"}],"responses":{"200":{"description":"Ignored event — acknowledged"},"201":{"description":"Post created from release"},"401":{"description":"Invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Unknown project or webhook not enabled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Payload too large (500 KB cap)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (30/hour/project)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Your wl_… api_token from signup (rotate on the dashboard). Create-post, import, and publish-only PATCH (a body of exactly {\"draft\": false}) also accept a per-project scoped CI token (wlp_…, issued/rotated/revoked on the project dashboard) — it authenticates only those operations for its own project."}},"parameters":{"slug":{"name":"slug","in":"path","required":true,"schema":{"type":"string"},"description":"Project slug"}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]},"LintRequest":{"type":"object","properties":{"changelog":{"type":"string","description":"CHANGELOG.md text to grade"},"url":{"type":"string","description":"Public https URL: GitHub/GitLab/Codeberg repo, raw CHANGELOG.md, feed, or hosted changelog page"},"format":{"type":"string","enum":["text"],"description":"Return a plain-text report instead of JSON"}}},"LintReport":{"type":"object","properties":{"ok":{"type":"boolean"},"grade":{"type":"string","enum":["A","B","C","D","F"]},"score":{"type":"integer","description":"0-100"},"entries_checked":{"type":"integer"},"checks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"status":{"type":"string","enum":["pass","warn","fail","info"]},"detail":{"type":"string"},"guide":{"type":["string","null"],"description":"URL of the guide explaining the fix"}}}},"issues":{"type":"array","description":"Specific entries to fix (up to 20 shown on the web report)","items":{"type":"object","properties":{"title":{"type":"string"},"msg":{"type":"string"}}}},"url":{"type":"string"},"report_url":{"type":"string"},"badge_url":{"type":"string"},"badge_markdown":{"type":"string"}},"required":["ok","grade","score","checks"]},"Project":{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"url":{"type":"string"},"unlisted":{"type":"boolean"},"posts":{"type":"integer","description":"Published post count"},"drafts":{"type":"integer"},"public_url":{"type":"string"}}},"Post":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"body_md":{"type":"string"},"tag":{"type":"string"},"created_at":{"type":"string","description":"UTC, YYYY-MM-DD HH:MM:SS"},"url":{"type":"string","description":"Permalink"},"reactions":{"type":"object","additionalProperties":{"type":"integer"},"description":"Emoji -> count (only present when non-zero)"}}},"ImportRequest":{"type":"object","properties":{"changelog":{"type":"string","description":"CHANGELOG.md text (max 400 KB)"},"url":{"type":"string","description":"Public https URL to fetch the changelog from (github blob URLs auto-rewritten to raw)"},"dry_run":{"type":"boolean","description":"Preview parse without writing"}}}}}}