Commune

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

  1. 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 in allowed_values, or drop it. param echoes the name you sent.
  2. A value outside the parameter's set

    The parameter exists, but the value is not one it accepts: a status that is not an article status, an expand path the operation cannot expand, a fields name the resource does not have, or a boolean parameter given something other than true or false. For comma separated parameters such as expand and fields, the first value that is not in the set is the one reported.

    Fix: Pick a value from allowed_values. Values are case sensitive and are sent as they appear there.
  3. 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. limit has 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 as status.

    Fix: Follow the constraint the message states (for example "Give a whole number between 1 and 100.") and send the parameter once.
  4. A cursor that cannot be read

    cursor is 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, limit and so on) between pages.

    Fix: Pass pagination.next_cursor from the previous response back unchanged, with exactly the same query parameters. If the filters have to change, start again from the first page without a cursor. Do not store cursors to resume later.
  5. A required parameter is missing

    Three parameters are required in some situations. metric on GET /newsletters/{newsletter}/timeseries has no default. q on GET /search has to be present (a query shorter than two characters answers with an empty page, not an error). And newsletter on GET /delivery-attempts/{attempt} and POST /delivery-attempts/{attempt}/replay may only be left out by a credential that reaches exactly one newsletter.

    Fix: Send the parameter the message names. For metric, pick one of allowed_values. For newsletter, send the newsletter's id or handle; GET /newsletters lists the ones your credential reaches.
  6. A write without a usable Idempotency-Key

    Every operation that changes something (every POST, PATCH and DELETE) requires an Idempotency-Key header, 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: Send Idempotency-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.
  7. 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 status on 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: a slug that is not lowercase letters, digits and single hyphens (at most 60 characters); a PATCH with no properties in it; content_markdown that does not parse (the message gives the line, and HTML is not accepted); a tag name that is empty or over 60 characters; a subscribers list that is empty, holds more than 500 ids, or holds something that is not a subscriber id; a test send to list that is empty, has more than 5 addresses, or holds something that is not an email address; a scheduled_for that is missing or is not an RFC 3339 timestamp; and acknowledge_broken_images that is not a boolean.

    Fix: Send Content-Type: application/json with 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 same Idempotency-Key.
  8. A time window that cannot be read

    On the operations that take since and until, a value was neither a calendar date nor an RFC 3339 timestamp, or since was later than until.

    Fix: Send dates such as 2026-08-01 (read as midnight UTC) or timestamps such as 2026-08-01T09:30:00Z, with since earlier than until. Leave until out 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

HTTP 400
{
  "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.