Cadence
Reference

Tasks

Create, read, and drive a task through its whole lifecycle.

A task is the unit of work in Cadence. It is addressed by a KEY-N id — the space key, a hyphen, and a number, such as OPS-42 — which never changes once the task is created.

This page is the complete reference for the task lifecycle. Each capability is reachable on one or more surfaces: the HTTP API (/api/v1), MCP tools, and the web interface. Where a capability is not on /api/v1, that is stated. See Authentication for the role each level needs; creating and changing tasks needs member, and forcing a transition needs admin.

Replace https://cadence.example.com with your instance's address.

Create a task

POST /api/v1/spaces/:space_key/tasks; MCP tasks_create. Needs member.

A new task lands on a workflow's default stage. Over MCP you must name the workflow, with workflow_slug. Over the API you may name it with workflow; without workflow, the task lands on the default stage of the space's first active workflow.

Fields:

FieldNotes
titleRequired.
bodyFree text.
priorityOne of P0, P1, P2, P3. Defaults to P2.
sequence_labelOne or two capital letters and a number, such as A1, B2, C10.
due_atA date-time.
workflowAPI only: the slug of a workflow in the space. The task lands on that workflow's default stage. Over MCP, the required workflow_slug does the same. Over the API, without workflow, the task lands on the default stage of the space's first active workflow.
task_typeA task-type slug in the space. Left unset, it defaults to the named workflow's default type, then the space's default. With no workflow named, it defaults to the space's default type.
tagsA list of tag names in the space.
parent_task_keyThe KEY-N id of a parent task in the same space.
colorOne of gray, red, orange, amber, yellow, green, teal, blue, indigo, purple, pink.
afkMarks the task as autonomous (unattended). Boolean; defaults to false. Rules can be scoped to this flag — see Stage rules.

Over the API, a create call must carry an Idempotency-Key header, so a retried request cannot create a second task:

curl -X POST https://cadence.example.com/api/v1/spaces/OPS/tasks \
  -H "Authorization: Bearer smc_your_token_here" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1e-first-task" \
  -d '{ "title": "Ship the release notes", "priority": "P1" }'

The response is the created task:

{
  "task": {
    "key": "OPS-42",
    "space": "OPS",
    "title": "Ship the release notes",
    "priority": "P1",
    "stage": "Backlog",
    "workflow": { "slug": "delivery", "name": "Delivery" },
    "gate_status": "open"
  }
}

The gate_status field is open or blocked. A task reads blocked when it is on hold, when a required check at its current stage has not passed, or when a dependency that blocks at its current stage is unresolved; otherwise it reads open. Cadence derives this field when the task is read — you cannot set it — and you can filter tasks_list by it.

Naming a workflow over the API that the task cannot land on is refused with 422 and invalid_request, with one of these messages:

  • unknown workflow — no workflow in the space has that slug.
  • workflow is archived
  • workflow has no stages — every stage of the workflow is archived, or it has none.
{ "error": { "code": "invalid_request", "message": "unknown workflow" } }

A create with no Idempotency-Key header is refused with 422 and missing_idempotency_key. Reusing a key with a different request body is refused with 422 and idempotency_fingerprint_mismatch; the same key with the same body replays the first result rather than creating again.

Read a task

GET /api/v1/tasks/:key; MCP tasks_get; the task's page in the browser. Needs read.

curl https://cadence.example.com/api/v1/tasks/OPS-42 \
  -H "Authorization: Bearer smc_your_token_here"

The task carries its key, title, body, priority, stage, stage_group, workflow, task_type, assignee, participants, tags, timestamps, parent, gate_status, and the hold fields hold_reason and hold_kind. Both hold fields are always present: when the task is not held they are null, and when it is held hold_reason carries the reason and hold_kind is the effective kind (blocking or advisory, defaulting to blocking if none was recorded). The tasks_get tool additionally returns attachments_count. A missing task returns 404 and task_not_found.

List tasks

GET /api/v1/spaces/:space_key/tasks; MCP tasks_list. Needs read.

Filter with any of archived (true, false, or all; defaults to false), resolved, priority, gate_status, stage, stage_group, assignee (none or user:<id>), tags, updated_since, blocked, parent, has_children, and afk. Results are newest-first, in pages of up to 50 (200 maximum); follow next_cursor with the cursor filter to read the next page.

{ "tasks": [ … ], "next_cursor": "…" }

An invalid filter value is refused with 422 and code invalid_filter, naming the filter at fault. Over MCP, passing limit: 0 returns a summary of the matching tasks — a total count, and breakdowns by_stage and by_assignee — instead of the rows.

The blocked filter

blocked=true returns the tasks that are held up in some way; blocked=false returns the rest. Reachable with read. A task counts as blocked when any of these holds:

  • It is on hold — it carries a hold reason, of either kind.
  • A required check at its current stage has not passed.
  • A lease for it is held pending at a capacity ceiling — the pool, space, or instance is full — and none of its leases is running. A lease that is merely booting or waiting on the runtime does not count here; only a lease held at a genuine capacity ceiling does. This is the same condition the board card shows as its Awaiting capacity badge, and the same pending wait a lease's card chip names (see how a queued lease reads on its board card).
  • It has an uncleared dependency edge — a blocker that has not reached the state the edge waits for. This counts whether the edge blocks the task now or only from a later stage; a cleared edge does not count.
  • It is waiting on a WIP slot — a workflow WIP constraint is holding it out of the capped stages.

On the board card, the same states read as badges, and two are worth calling out:

  • Awaiting capacity now means only a genuine capacity ceiling — a lease held pending because the pool, space, or instance is full. A lease that is only booting, waiting on the runtime, or awaiting its agent no longer reads as awaiting capacity; its own lease chip names that wait instead.
  • Waiting · Blocks from <stage> flags an uncleared blocker whose edge does not block the task at its current stage yet — it will start blocking at the named stage. It stops nothing today, so it is shown only so you can tell a task with an outstanding blocker ahead from one with none.

Search tasks

MCP tasks_search. Needs read. There is no /api/v1 route for task search; for a combined tasks-and-docs search over HTTP, see Search.

Pass space_key, the query as q, and optionally limit (1 to 100, default 25). The result is { "tasks": [ … ] }. Each hit is the task, with its ref, a snippet and a rank.

  • If the whole query is a task's KEY-N id, such as OPS-42, that task comes first. Its snippet is null, it is not repeated further down, and it takes one of the limit places. The key must belong to a task in the space you are searching.
  • Every other hit is a task whose title or body matches. It carries a plain-text snippet with the matched words wrapped in <mark>, and the hits are ordered by rank, highest first. A task that matched only on its title has no <mark> in its snippet.

Archived tasks are never returned. When a worker's lease calls tasks_search, it sees only the tasks its scope covers. See Leases § What a lease can reach.

The last word of the query matches as a prefix, and a query shorter than 2 characters returns nothing. For the full matching rules, see Search.

Update a task

PATCH /api/v1/tasks/:key; MCP tasks_update; web per-field edit. Needs member.

The editable fields include title, body, priority, sequence_label, due_at, tags, task_type, task_group_id, parent_task_key (or parent_task_id, the same parent given by its numeric id), color, position, and afk. A field the endpoint does not recognise is refused with 422 and unpermitted_fields, listing the fields that cannot be set.

Move a task between stages

POST /api/v1/tasks/:key/transition; MCP tasks_transition; web. Needs member.

Pass the target stage by name. The task moves to that stage on its current workflow, subject to the stage's rules — a required check that has not passed, or a required property that is not set, will refuse the move. See Stage rules and What moves a task without a person.

{ "stage": "In review" }

A stage name that does not exist on the workflow is refused with unknown_stage and the message no stage named "In review" on this workflow. An empty stage is refused with stage required.

Passing force: true bypasses the target stage's check and property gating, its task-type restriction and any WIP constraint. Forcing a transition needs admin in the task's space, so a worker's lease can never force one, and the bypass is recorded. The browser offers a forced move only when required checks refused it. See Web interface § Moving a task from its page.

Move a task to another workflow

MCP tasks_transfer; web. Needs member. There is no /api/v1 route.

Pass the required workflow_slug, and optionally a stage on that workflow; without a stage the task lands on the destination workflow's default stage. An unknown workflow is refused with unknown workflow_slug, a workflow with no stages that are not archived with destination workflow has no stages, and an unknown stage with no stage named "…" on this workflow. A destination that is archived is refused with destination workflow is archived, and naming the workflow the task is already on with destination workflow is the same as the source — to move a task within its workflow, transition it instead.

A transfer is also refused while the task has an active lease on it — with one exception. When the caller is a worker moving the very task it is dispatched against, that worker's own lease does not block the move: it need not cancel itself first. Every other active lease still blocks. When you move a task yourself — in the browser, or over MCP as a user rather than as a lease — no lease is exempt: any active lease on the task blocks the move.

When an active lease blocks, the refusal names each blocking lease by its pool and state, for example:

cannot transfer: 1 active lease (Delivery workers — running). Cancel the lease and wait for it to reach a terminal state, then retry.

Cancel the leases it names (see Leases) and wait for each to reach a terminal state, then transfer again.

A successful transfer adds a transferred entry to the task's activity feed. Its payload carries from_workflow and to_workflow (the workflow names), from_workflow_id and to_workflow_id, from_stage and to_stage (the stage names), from_stage_id and to_stage_id, and by. Transfers recorded before workflows took their current name carry from_board, from_board_id, to_board, and to_board_id in place of the four workflow fields, and those entries are not rewritten. A script that reads transferred entries from activities_list must accept both sets of names.

Assign a task

POST /api/v1/tasks/:key/assign; MCP tasks_assign; web. Needs member.

Set assignee to user:<id>, pool:<id>, or null to unassign:

{ "assignee": "user:4088" }

An unknown user is refused with unknown user user:<id>, an unknown pool with unknown agent pool pool:<id>, and a malformed value with assignee must match user:<id> or pool:<id>.

Assigning to pool:<id> creates a lease of that pool on the task straight away, whatever stage the task is at, and the task's timeline shows an assigned entry. It needs no assignment rule, and it does not check a hold, a dependency or a required check. If a lease of that pool on the task is still active, that lease is reused and no second one is created. While the task has a blocking hold, the worker is refused when it tries to start; otherwise it runs. A worker's lease can call tasks_assign on any task its scope reaches. See What moves a task without a person § A person or a worker assigns a task to a pool.

Resolve and unresolve

POST /api/v1/tasks/:key/resolve and .../unresolve; MCP tasks_resolve and tasks_unresolve. Needs member.

Resolving marks a task done without moving or archiving it; unresolving reopens it.

Archive and unarchive

POST /api/v1/tasks/:key/archive and .../unarchive; MCP tasks_archive and tasks_unarchive. Needs member.

Archiving is a soft removal: the task drops out of default listings but is preserved and can be unarchived.

Holds

A hold stops a task from being worked. There are two kinds.

  • tasks_set_hold (MCP only) sets a hold. Pass a reason, and optionally a kind — blocking (the default) or advisory. A blocking hold suppresses dispatch and lease claims and discards any queued work; an advisory hold records a note without suppressing anything.
  • tasks_clear_hold (MCP and web; no /api/v1 route) clears a hold, with an optional instruction.

Setting and clearing a hold needs member. tasks_clear_hold clears a blocking hold only for a human operator: a worker or other non-human principal that tries is refused with clear_hold_requires_human. An advisory hold may be cleared by any principal.

A worker can still get past a blocking hold without a person. tasks_set_hold accepts a lease on any task its scope reaches, and setting kind: "advisory" replaces the blocking hold with an advisory one, which the worker may then clear. Each change is recorded on the task's timeline as a hold_set entry naming who made it. So a blocking hold alone does not guarantee a task waits for a person. See Web interface § Making a task wait for a person and What moves a task without a person.

Nudge

MCP tasks_nudge (MCP only). Needs member.

Pass a message. A nudge re-dispatches a stalled task at its current stage, carrying the message to the agent that picks it up. The response reports whether work was re-dispatched. A non-human principal cannot nudge a task on a blocking hold; it is refused with nudge_requires_human.

A nudge also clears the task's hold, if it has one, and resets the count of lost leases on every pool. Before it starts the stage again, it cancels the task's leases that have not started running; a worker already running is left alone.

A nudge turns off the lost-lease hold

Cadence starts a pool again on its own after one of its leases on the task is lost, and holds the task on the third lost lease, only when all of these are true:

  • the lease was lost less than 24 hours ago
  • the lease was created after the task entered its current stage
  • an assignment rule at the task's current stage names the pool and applies to the task
  • the pool is not archived and has no lease active on the task
  • no later lease of that pool on the task ended in a state other than lost

Starting the pool again, but not the hold, also needs the task to have no blocking hold, no unresolved dependency that blocks at its current stage, and no unpassed required check. While one of those stops it, Cadence tries again about once a minute, and starts the pool once all of them are true, as long as the conditions above still hold. Once the lease was lost more than 24 hours ago, nothing starts the pool again on its own. Cadence counts the pool's lost leases on the task at every stage, not only the current one, until the count is reset, so the hold can come sooner than three losses at this stage.

So a pool that no assignment rule at the stage names, such as one started only by an assignment, a scheduled rule or an inbound rule, is never started again this way and never gets this hold. When the hold is placed, its reason reads dispatch blocked — manual intervention needed: <pool> (3 attempts). See What moves a task without a person § A blocking hold waits for a person to clear it.

Once that hold has been placed for a pool, Cadence treats the pool as blocked until one of these resets it: a clear of the task's hold with tasks_clear_hold or Clear hold, a call to tasks_dispatch_pool or a click on Dispatch while the task has a hold, or tasks_dispatch_unblock for that pool. A nudge does not reset it. If you nudge the task while the pool is still blocked, Cadence does not place that hold again for that pool. The next time three leases of that pool are lost, it stops starting the pool and places no hold, so the task sits idle at its stage.

What matters is whether the pool has been reset since its (3 attempts) hold was placed, not which hold the task carries when you nudge it. A hold that replaced the (3 attempts) hold, such as a stage-loop hold, does not reset the pool. A move to another stage on the workflow resets every pool only when the task has an advisory hold at the time, because Cadence clears that hold on the move and records a hold_cleared entry. A move of a task with a blocking hold or no hold, and a move to another workflow with tasks_transfer, leave the pool blocked. A worker may nudge a task that has an advisory hold or no hold, so a worker can turn this safeguard off with no person involved.

To tell whether a pool is still blocked, read the task's timeline with activities_list task_key=OPS-42. The pool is blocked when its latest dispatch_blocked entry, whose pool_id names the pool, is newer than every hold_cleared entry and every dispatch_unblocked entry for that pool. If you are not sure, do not rely on reading it: call tasks_dispatch_unblock key=OPS-42 with no pool_id straight after any nudge. It resets every pool that is still blocked and lists them in cleared. An empty cleared means no pool was blocked, and the call starts nothing.

Because a worker can nudge with no person involved, decide how you will catch it. tasks_dispatch_unblock needs the same role as tasks_nudge, so if your agent skills tell workers to nudge, tell them to call tasks_dispatch_unblock straight after. Otherwise check for idle tasks from time to time with the steps in Find and restart a task left idle after a nudge.

On a workflow with auto-advance on, the abandoned-work hold may catch an idle task, but only when every condition listed there holds: among them, the stage has abandoned_work_detection on, the task has no unresolved dependency that blocks at its current stage, every lease of the stage's assigned pools has ended and the newest more than 3 minutes ago, and a check that the stage's create check rules expect is still missing or pending. Otherwise nothing points you to it.

To keep the safeguard, reset the pool rather than nudge: clear the hold with tasks_clear_hold or Clear hold. If you have already nudged, call tasks_dispatch_unblock on the task straight after (see Dispatch to a pool).

Find and restart a task left idle after a nudge

A task left idle this way has no hold and no active lease on the pool. Its timeline (activities_list task_key=OPS-42) shows a nudged entry, then lease_ended entries with "outcome": "lost" for the pool, and after the last of them no new lease of that pool and no hold_set entry. To find candidates, leases_list space_key=OPS state=lost lists the space's lost leases, newest first, each with its task and pool.

To restart it:

  1. Call tasks_dispatch_unblock key=OPS-42 pool_id=19. This puts the safeguard back for that pool and resets its count of lost leases. In the response, cleared lists the pool's id: [19]. It then dispatches the task at its current stage, but only while the task has no blocking hold, no unresolved dependency that blocks at its current stage and no unpassed required check; it does not touch a hold, so while the task has a blocking hold it starts nothing until a person clears that hold. When it does dispatch, it starts every pool, not only the one you named, whose assignment rule at the stage applies to the task, that is not archived, and that has no lease active on the task the rule leases. That can include a pool whose work at the stage already finished. A rule with target: parent leases the task's parent, not this task.
  2. If that did not start the pool, call tasks_dispatch_pool key=OPS-42 pool_id=19. This dispatches only the pool you name, through its assignment rule at the stage, and no other pool. It clears a hold the task has, and is refused for a worker if that hold is blocking. It starts the pool only while the task has no unresolved dependency that blocks at its current stage, no unpassed required check, and no lease of that pool is active on the task the rule leases.

Dispatch to a pool

MCP tasks_dispatch_pool and tasks_dispatch_unblock; web. Needs member. Neither has an /api/v1 route.

  • tasks_dispatch_pool re-dispatches one pool at the task's current stage, clearing the task's hold if it carries one. Pass key and pool_id. The pool is eligible once any of its leases on the task has reached a terminal state since the task entered its current stage, even if a newer lease of that pool is still running. An unknown pool is refused with unknown pool id. A pool with no such lease is refused with pool_not_redispatchable ("pool has no terminal-state lease since the task entered its current stage"), and the hold is left in place. Clearing a hold this way carries the same human-only rule as clearing it directly: if the task is under a blocking hold, only a human operator may dispatch a pool on it — a non-human principal is refused with dispatch_pool_requires_human ("only a human operator may dispatch a pool on a held task"), so a stuck agent cannot clear its own blocking hold and retry. If the pool is eligible, the hold is always cleared, but a new lease is created only through an assignment rule at the task's current stage that names the pool and applies to the task (see Stage rules § How a rule's scope is chosen), only while the task has no unresolved dependency that blocks at its current stage and no unpassed required check, and only while no lease of that pool is active on the task the rule leases. A rule with target: parent leases the task's parent, not this task, and starts nothing if the task has no parent or the parent is not of the type the rule names. Otherwise it starts nothing. To run a pool that no assignment rule at the stage names, assign the task to it again (see Assign a task). A scheduled rule's pool runs again at the rule's next due time (see Scheduled rules). An inbound rule's pool runs again when a new matching event arrives, or when an event that was already waiting in the dispatch queue reaches its turn. A waiting event starts the pool with no new event and no person, but only if, when its turn comes, the task has no blocking hold, no unresolved dependency that blocks at its current stage and no unpassed required check. If one of those stops it, the event is closed without starting anything and is not tried again; an event whose turn came while the hold was still on has already been closed this way and does not run after the clear. Cadence takes the waiting events for a task and pool one at a time: on each pass it closes or starts only the oldest. So while the hold is on, it closes them one by one, and if you clear the hold before it has closed them all, the next one starts the pool after the clear. To stop the waiting events from running, remove each of them with inbound_queue_remove before you clear the hold. Listing and removing waiting events both need admin; a member asks a space admin to do it. Find each event's id with inbound_queue_list space_key=OPS: they are the waiting rows whose subject is the task and whose pool_id is the pool. If no event for the task and pool is still waiting, Cadence will not start that pool on the task again until a new matching event arrives. To run it sooner, assign the task to the pool yourself (see Assign a task). Assigning does not use up an event that is still waiting: the event waits while the lease you started is active, and once that lease ends it starts the pool again, with no person, if the task passes the same conditions. If you do not want that second run, remove the waiting event first. The lease you start waits, pending, while the pool is paused, the space is archived, or the pool, the space or the instance has no room, and starts once all of them allow it. To check whether an event was closed, list state=done rows; the event's delivery in inbound_deliveries_list, which also needs admin, is marked failed with the gate's reason (see Inbound § The dispatch queue and Inbound § When a dispatch mints nothing).

  • tasks_dispatch_unblock clears the dispatch-blocked state that three lost leases of a pool leave on the task (see Nudge § A nudge turns off the lost-lease hold). A failed lease does not leave this state; it puts the task on a hold instead. Pass key, and optionally a pool_id to unblock one pool rather than every blocked pool. It does not touch a hold. For each pool it unblocks, it resets the pool's count of lost leases, so the pool gets three more tries, and it puts back the lost-lease hold: after it, the third lost lease on that pool holds the task again. That is why you call it after a nudge. The response lists the unblocked pools' ids in cleared. A pool you name that is not blocked is listed in already_clear instead, and nothing is reset or started for it.

    When it unblocks a pool, it dispatches the task at its current stage. That starts nothing while the task has a blocking hold, an unresolved dependency that blocks at its current stage, or an unpassed required check. Because this call does not clear a hold, a task still under its (3 attempts) hold stays idle until a person clears the hold. Otherwise it starts every pool, not only the ones it unblocked, whose assignment rule at the stage applies to the task, that is not archived, and that has no lease active on the task the rule leases. A rule with target: parent leases the task's parent, not this task.

Participants

MCP tasks_add_participant, tasks_remove_participant (both MCP and web), and tasks_list_participants (MCP only). None have an /api/v1 route. Adding and removing need member; listing needs read.

A participant is a user in one of four roles: owner, reporter, observer, or assignee. A task has at most one assignee. Pass user as user:<id> and a role.

Comments and the activity feed

MCP activities_create and activities_list; POST /api/v1/tasks/:key/activities and GET /api/v1/tasks/:key/activities; web comment and timeline. Commenting needs member; reading needs read.

activities_create appends a comment: pass task_key and a body. activities_list reads the timeline: pass task_key, and optionally limit (up to 200), order (asc or desc, newest-first by default), and since_id to read only entries after a given one. Each entry carries its id, kind, author, author_name, payload, and created_at.

Over the API, GET /api/v1/tasks/:key/activities reads the same entries, and can also filter them by kind. Archived and resolved tasks are served like any other. It takes these query parameters:

ParameterNotes
orderasc or desc. Defaults to desc, newest first.
since_idA positive integer. Returns only entries whose id is larger.
limitAn integer from 1 to 200. Defaults to 50.
kindA comma-separated list of activity kinds, such as comment,hold_set. Returns only entries of those kinds. API only: MCP activities_list takes no kind.
curl "https://cadence.example.com/api/v1/tasks/OPS-42/activities?order=asc&kind=comment" \
  -H "Authorization: Bearer smc_your_token_here"

The response wraps the entries in activities:

{
  "activities": [
    {
      "id": 9182,
      "kind": "comment",
      "author": "user:7",
      "author_name": "Sam Rivera",
      "payload": { "author": "user:7", "body": "Release notes drafted." },
      "created_at": "2026-10-10T09:14:02Z"
    }
  ]
}

A bad parameter is refused with 422 and invalid_request, with one of these messages:

  • order must be "asc" or "desc"
  • since_id must be a positive integer
  • limit must be an integer between 1 and 200
  • kind must name at least one activity kind
  • unknown activity kind: "<kind>"

A task key that does not exist is refused with 404 and task_not_found.

Post a comment over the API

POST /api/v1/tasks/:key/activities appends a comment, the same as activities_create. It needs a user API token with member on the space, and it requires an Idempotency-Key header. Send the comment as body:

curl -X POST https://cadence.example.com/api/v1/tasks/OPS-42/activities \
  -H "Authorization: Bearer smc_your_token_here" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 3c9a-release-notes-comment" \
  -d '{ "body": "Release notes drafted." }'

Cadence answers 201 with the new entry under activity, in the same shape activities_list returns:

{
  "activity": {
    "id": 9182,
    "kind": "comment",
    "author": "user:7",
    "author_name": "Sam Rivera",
    "payload": { "author": "user:7", "body": "Release notes drafted." },
    "created_at": "2026-10-10T09:14:02Z"
  }
}
FieldNotes
bodyRequired. The comment text. It must not be blank.
kindOptional. If you send it, it must be "comment".

If you send the same key with the same body again within 24 hours, Cadence replays the first response and does not post a second comment, so a post is safe to retry after a timeout. See HTTP API § Idempotency keys.

A post is refused with:

  • 404 and task_not_found — the task key does not exist.
  • 403 and lease_forbidden — the call used a lease's token on a task the lease can reach (Posting a comment requires a user API token.). A worker comments with MCP activities_create instead. A lease's token on any other task gets the usual 403 refusal, such as scope_violation; see HTTP API § A 403 refusal.
  • 422 and missing_idempotency_key — no Idempotency-Key header (Idempotency-Key header required).
  • 422 and idempotency_fingerprint_mismatch — the key was already used with a different body (Idempotency-Key reused with a different request body).
  • 422 and invalid_request, with one of these messages:
    • kind must be "comment"
    • comment body cannot be blank
    • comment body cannot contain a null byte (U+0000)

A 422 with invalid_request is also replayed for its key, so after you fix the body, send it with a new key.

On this page