This page speaks HTTP. Requests go to https://deploylog.dev/api/cli/... and authenticate with an API key in the Authorization: Bearer dk_... header; the CLI and the GitHub Action use the same routes. The CLI is chapter 05, the GitHub Action chapter 06, and API keys chapter 11. Every value below is the one the route's schema enforces.
Limits
An entry is one changelog item: a title, a markdown body, an optional type and an optional version, the release label an entry carries.
| Field | Limit |
|---|---|
| Entry title | 1 to 200 characters |
| Entry body (markdown) | 1 to 50,000 characters, counted as characters, not bytes |
| Project name (a project is one changelog you publish to) | 1 to 100 characters |
| Project website URL | a valid URL, up to 2,000 characters |
| Organization name (an organization owns projects and keys) | 1 to 100 characters |
| API key name | 1 to 100 characters |
| List page size | 1 to 50, default 50 |
| AI summarize commits | up to 200 messages, each up to 500 characters |
| AI summarize release notes | up to 20,000 characters |
| Manual chapter number | up to 16 characters |
| Manual chapter title | up to 200 characters |
| Version label | up to 60 characters; draft is refused |
| Widget accent (the colour of entry titles in the embedded widget) | 6-digit hex, for example #18181b |
Exceed any of these and the request comes back 400 with code VALIDATION_ERROR and a details object of per-field errors. Nothing is truncated and nothing is clamped.
Free-plan quotas are counts, not rates: 3 projects, 5 AI summaries per month, 5 manual generations per month (a manual generation is one run that drafts a page of your product manual from its code). On Free, a fourth project, a sixth summary or a sixth generation in a month is refused.
Entry types and statuses
Five types: feature, fix, improvement, breaking, announcement. Send one of those or nothing.
- Type is optional and may be null. An entry with no type is legal.
- An entry is draft or published, never toggled. You send the target state as an explicit
trueorfalse, so re-running a publish cannot unpublish. - Version is optional. It may be omitted or sent as an empty string. When given it must be exactly three dot-separated numbers,
1.2.3, with no leadingv. Anything else is400VALIDATION_ERROR. - A slug, the URL name of an entry, is never supplied at creation. The server derives it from the title. On update you may send a slug override as loose text of up to 200 characters; it is trimmed, normalised server-side, and rejected with
400VALIDATION_ERRORpast 200.
The error envelope
Every error is a JSON object with one error field holding a code and either a message or, for validation, a details object of field errors.
| Code | Status | When | Example body |
|---|---|---|---|
UNAUTHORIZED |
401 | No usable API key on the request | {"error":{"code":"UNAUTHORIZED","message":"Valid API key required"}} |
FORBIDDEN |
403 | Key authenticated, permission missing | {"error":{"code":"FORBIDDEN","message":"API key lacks write permission"}} |
NOT_FOUND |
404 | No project with that slug in your organization | {"error":{"code":"NOT_FOUND","message":"Project 'acme' not found"}} |
VALIDATION_ERROR |
400 | Body or query failed a limit or format above | {"error":{"code":"VALIDATION_ERROR","details":{"formErrors":[],"fieldErrors":{"title":["Title is required"]}}}} |
CONFLICT |
409 | The entry write collides with an existing row | {"error":{"code":"CONFLICT","message":"..."}} |
RATE_LIMITED |
429 | Bucket exhausted | {"error":{"code":"RATE_LIMITED","message":"Too many requests. Please slow down and try again shortly."}} |
SERVER_ERROR |
500 | The write failed on our side | {"error":{"code":"SERVER_ERROR","message":"..."}} |
A successful entry create returns 201 with the row under data.
Rate limits
A bucket is one counter for one kind of request. Every bucket is a sliding window. Nothing resets on the minute boundary; the window trails the request. Cross a bucket's limit inside its window and the request is rejected with the 429 described under the table.
| Bucket | Limit | Window |
|---|---|---|
| API and CLI writes, counted per organization | 60 | 1 minute |
| Dashboard AI | 60 | 1 minute |
| Widget data | 60 | 1 minute |
| Widget analytics | 120 | 1 minute |
| Subscribe, counted per client address | 5 | 1 hour |
| Subscribe, counted per email address | 3 | 1 hour |
| Unsubscribe | 20 | 1 hour |
| Billing webhooks (events Stripe sends DeployLog), counted per signature | 200 | 1 minute |
Every rate-limited response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. X-RateLimit-Reset is whole seconds. Retry-After is added only on a rejection, and a rejection is 429 with the RATE_LIMITED body above.
A client-supplied forwarded-for header never changes which address is counted. The address comes from the platform-set headers only, falling back to a single fixed bucket when neither is present.
Keys and plans
Writing through the API is open on every plan, free included; no paid gate is enforced on writes, and the quotas above are the only limits a free organization meets.
An API key defaults to both read and write when no permission is named, and at least one permission is required. Ask for none and the request is 400 VALIDATION_ERROR with the message At least one permission required.