400bad_request
Malformed request
Something in the request itself is wrong: a query parameter, a header or the body. Change the request and send it again.
The API could not accept the request as written. The problem is always in what you sent (the query string, the Idempotency-Key header, or the JSON body of a write), never in the state of the thing you addressed, so the fix is always a change to the request.
The envelope tells you what to change. param names the one parameter at fault when there is one, spelled exactly as it should be sent. When that parameter accepts a finite set, allowed_values lists the whole set, and message repeats it in prose. For an unknown parameter name, allowed_values holds the parameter names the operation accepts rather than values. Errors in a request body carry no param; the message names the property instead.
An unsupported Commune-Version header has its own code, invalid_version, because the fix is different.
Why it happens, and how to fix it
A query parameter the operation does not have
You sent a query parameter the operation does not declare, usually a typo or a name from another API (
?state=for?status=,?page=for?cursor=,?per_page=for?limit=). It is refused rather than ignored, because a filter that silently did nothing would look like a filter that matched everything.Fix: Rename it to one of the names inallowed_values, or drop it.paramechoes the name you sent.A value outside the parameter's set
The parameter exists, but the value is not one it accepts: a
statusthat is not an article status, anexpandpath the operation cannot expand, afieldsname the resource does not have, or a boolean parameter given something other thantrueorfalse. For comma separated parameters such asexpandandfields, the first value that is not in the set is the one reported.Fix: Pick a value fromallowed_values. Values are case sensitive and are sent as they appear there.A value of the wrong shape
The value is not a set member, it is a number, an identifier or a string, and it failed that check.
limithas to be a whole number between 1 and 100. An identifier parameter has to be a UUID. Some strings have a maximum length. A comma separated parameter needs at least one value. And a parameter may only be given once, unless it is a repeatable filter such asstatus.Fix: Follow the constraint the message states (for example "Give a whole number between 1 and 100.") and send the parameter once.A cursor that cannot be read
cursoris opaque and only valid for the operation and the filters that produced it. It is refused when it was edited, truncated or URL decoded twice, when it came from a different operation, or when you changed a filter (status,expand,limitand so on) between pages.Fix: Passpagination.next_cursorfrom the previous response back unchanged, with exactly the same query parameters. If the filters have to change, start again from the first page without acursor. Do not store cursors to resume later.A required parameter is missing
Three parameters are required in some situations.
metriconGET /newsletters/{newsletter}/timeserieshas no default.qonGET /searchhas to be present (a query shorter than two characters answers with an empty page, not an error). AndnewsletteronGET /delivery-attempts/{attempt}andPOST /delivery-attempts/{attempt}/replaymay only be left out by a credential that reaches exactly one newsletter.Fix: Send the parameter the message names. Formetric, pick one ofallowed_values. Fornewsletter, send the newsletter'sidorhandle;GET /newsletterslists the ones your credential reaches.A write without a usable Idempotency-Key
Every operation that changes something (every
POST,PATCHandDELETE) requires anIdempotency-Keyheader, and refuses the request without one. A key is also refused when it is longer than 255 characters or contains spaces or characters outside printable ASCII.Fix: SendIdempotency-Key: <value>with a fresh value, a UUID being the usual choice, for each change you intend to make. When you retry that change, send the same value again so Commune replays the first answer instead of making the change twice.A request body the operation cannot read
On a write, the body was not valid JSON, was not a JSON object, named a property the operation does not write (such as
statuson an article, which is moved by the send and schedule operations), gave a property the wrong type or a value that is too long, or failed a rule of its own. The common ones: aslugthat is not lowercase letters, digits and single hyphens (at most 60 characters); aPATCHwith no properties in it;content_markdownthat does not parse (the message gives the line, and HTML is not accepted); a tagnamethat is empty or over 60 characters; asubscriberslist that is empty, holds more than 500 ids, or holds something that is not a subscriber id; a test sendtolist that is empty, has more than 5 addresses, or holds something that is not an email address; ascheduled_forthat is missing or is not an RFC 3339 timestamp; andacknowledge_broken_imagesthat is not a boolean.Fix: SendContent-Type: application/jsonwith a JSON object that follows the rule the message states. The message names the property, the limit and, for Markdown, the line. Nothing was written, so you can send the corrected body with the sameIdempotency-Key.A time window that cannot be read
On the operations that take
sinceanduntil, a value was neither a calendar date nor an RFC 3339 timestamp, orsincewas later thanuntil.Fix: Send dates such as2026-08-01(read as midnight UTC) or timestamps such as2026-08-01T09:30:00Z, withsinceearlier thanuntil. Leaveuntilout to run the window to now.
Retrying
No: the same request fails the same way, so change what param or the message names and send it again (a write that failed this way made no change, so the same Idempotency-Key can be reused).
Example response
{
"error": {
"code": "bad_request",
"message": "Unsupported value \"published\" for query parameter \"status\". Allowed values: draft, scheduled, sending, sent, failed, archived.",
"param": "status",
"allowed_values": [
"draft",
"scheduled",
"sending",
"sent",
"failed",
"archived"
],
"request_id": "req_01j9c8h1q7m3n4p5r6s7t8u9v0",
"docs_url": "https://usecommune.dev/errors/bad_request"
}
}Often confused with invalid_version, unprocessable, insufficient_scope.