[{"data":1,"prerenderedAt":44},["ShallowReactive",2],{"$fwsz4dn6hlfz1":3},{"href":4,"title":5,"description":6,"kind":7,"mark":7,"planned":8,"contributors":9,"provenance":7,"html":10,"headings":11},"\u002Fdocs\u002Fapi\u002Fconventions","Conventions","What every call has in common: requests and responses, errors, pagination, and which writes are safe to send twice.",null,false,[],"\u003Ch2 id=\"requests\">Requests\u003C\u002Fh2>\n\u003Cp>Every route is under \u003Ccode>https:\u002F\u002Fapi.zerohuman.com\u002Fv1\u003C\u002Fcode>, and every call carries a token in its \u003Ccode>Authorization\u003C\u002Fcode> header\n(\u003Ca href=\"\u002Fdocs\u002Fapi\u002Fauthentication\">Authentication\u003C\u002Fa>):\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-bash\">curl &quot;https:\u002F\u002Fapi.zerohuman.com\u002Fv1\u002Fruns?status=failed&quot; \\\n  -H &quot;Authorization: Bearer zhos_…&quot;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cul>\n\u003Cli>A request body is JSON. Send \u003Ccode>content-type: application\u002Fjson\u003C\u002Fcode> with it: without that header the body is not read\nas JSON.\u003C\u002Fli>\n\u003Cli>A body larger than 100 KB is refused with \u003Ccode>413\u003C\u002Fcode>. A body that is not valid JSON is refused with \u003Ccode>400\u003C\u002Fcode>.\u003C\u002Fli>\n\u003Cli>The API sends no CORS headers, so a web page on another site cannot read its answers. Call it from a server or a\nscript.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"responses\">Responses\u003C\u002Fh2>\n\u003Cp>Every answer is JSON, except a member's picture and an email attachment, which are the file itself.\u003C\u002Fp>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Status\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>200\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>A read, or a change that sends back what it changed.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>201\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>A \u003Ccode>POST\u003C\u002Fcode> that succeeded. Starting a run, deciding a gate and creating a task all answer \u003Ccode>201\u003C\u002Fcode>.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>204\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>A change with nothing to send back, such as deleting a task. The body is empty.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cul>\n\u003Cli>\u003Cstrong>Ids\u003C\u002Fstrong> are UUIDs, as strings. An execution's id is the id of the run it started at.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Times\u003C\u002Fstrong> are ISO 8601 strings in UTC: \u003Ccode>2026-09-28T09:00:00.000Z\u003C\u002Fcode>.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Money\u003C\u002Fstrong> says its unit in its name: \u003Ccode>costUsdCents\u003C\u002Fcode> and a spend cap's \u003Ccode>moneyCentsDay\u003C\u002Fcode> are US cents, and a task's\n\u003Ccode>moneyUsdPerRun\u003C\u002Fcode> is US dollars.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"errors\">Errors\u003C\u002Fh2>\n\u003Cp>A refused call answers with a \u003Ccode>4xx\u003C\u002Fcode> status and a JSON body that names why. The body comes in one of two shapes, and\nthe code you match on is in a different field in each:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{ &quot;error&quot;: &quot;api_token_scope&quot;, &quot;message&quot;: &quot;This token needs runs:write; it has runs:read.&quot; }\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cpre>\u003Ccode class=\"language-json\">{ &quot;message&quot;: &quot;task_version_conflict&quot;, &quot;error&quot;: &quot;Conflict&quot;, &quot;statusCode&quot;: 409 }\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cul>\n\u003Cli>\u003Cstrong>With no \u003Ccode>statusCode\u003C\u002Fcode>\u003C\u002Fstrong>, the code is \u003Ccode>error\u003C\u002Fcode>. \u003Ccode>message\u003C\u002Fcode>, when there is one, is a sentence for a person, and some\nrefusals add fields of their own (an invalid task definition lists its \u003Ccode>issues\u003C\u002Fcode>).\u003C\u002Fli>\n\u003Cli>\u003Cstrong>With \u003Ccode>statusCode\u003C\u002Fcode>\u003C\u002Fstrong>, the code is \u003Ccode>message\u003C\u002Fcode>, and \u003Ccode>error\u003C\u002Fcode> is only the name of the status.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Match on the code, not on the sentence. The statuses:\u003C\u002Fp>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Status\u003C\u002Fth>\n\u003Cth>What it means\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>400\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The request is missing something or asks for something invalid: a required field, a value the API does not accept, a task definition that could never run.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>401\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>No token, or not one that is valid. The body is \u003Ccode>{ &quot;message&quot;: &quot;Unauthorized&quot;, &quot;statusCode&quot;: 401 }\u003C\u002Fcode>.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>403\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The token cannot do this. It lacks the scope (\u003Ccode>api_token_scope\u003C\u002Fcode>), the route is the portal's alone, or what you asked to change is someone else's, such as a cloned task's definition (\u003Ccode>task_definition_read_only\u003C\u002Fcode>).\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>404\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>There is nothing with that id in your enterprise. An id from another enterprise answers exactly as one that does not exist.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>409\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The request conflicts with how things stand now: the task, or every member holding its role, is busy (\u003Ccode>overlap\u003C\u002Fcode>, \u003Ccode>member_busy\u003C\u002Fcode>), the gate is already decided (\u003Ccode>gate_not_waiting\u003C\u002Fcode>), someone saved a newer version (\u003Ccode>task_version_conflict\u003C\u002Fcode>), the slug is taken (\u003Ccode>task_slug_taken\u003C\u002Fcode>). Read the current state before you try again.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>413\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The body is over 100 KB.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>500\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Something failed on our side: \u003Ccode>{ &quot;statusCode&quot;: 500, &quot;message&quot;: &quot;Internal server error&quot; }\u003C\u002Fcode>.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Ch2 id=\"pagination\">Pagination\u003C\u002Fh2>\n\u003Cp>Six lists come a page at a time: tasks, runs, executions, webhook deliveries, the task catalogue, and a member's\nemail. They take the same query parameters:\u003C\u002Fp>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Parameter\u003C\u002Fth>\n\u003Cth>What it does\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>page\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Which page, counting from 1. The default is 1.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>limit\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>How many to a page, from 1 to 200. The default is 25, and a value outside the range is brought inside it.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>sort\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The field to sort by. Each list has its own, below; one it does not know sorts by its default.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>order\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>asc\u003C\u002Fcode> or \u003Ccode>desc\u003C\u002Fcode>. Anything other than \u003Ccode>asc\u003C\u002Fcode> is \u003Ccode>desc\u003C\u002Fcode>, which is the default.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>search\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Free text, matched against the list's own fields.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Any other parameter is a filter, such as \u003Ccode>GET \u002Fv1\u002Fruns?taskId=…&amp;status=failed\u003C\u002Fcode>. A filter a list does not know is\nignored, not refused.\u003C\u002Fp>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>List\u003C\u002Fth>\n\u003Cth>\u003Ccode>sort\u003C\u002Fcode> (default first)\u003C\u002Fth>\n\u003Cth>Filters\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>GET \u002Fv1\u002Ftasks\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>slug\u003C\u002Fcode>, \u003Ccode>name\u003C\u002Fcode>, \u003Ccode>kind\u003C\u002Fcode>, \u003Ccode>status\u003C\u002Fcode>, \u003Ccode>createdAt\u003C\u002Fcode>, \u003Ccode>updatedAt\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>teamId\u003C\u002Fcode>, \u003Ccode>status\u003C\u002Fcode>, \u003Ccode>kind\u003C\u002Fcode>, \u003Ccode>assigneeRole\u003C\u002Fcode>, \u003Ccode>origin\u003C\u002Fcode>; \u003Ccode>name\u003C\u002Fcode> and \u003Ccode>slug\u003C\u002Fcode> match any part\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>GET \u002Fv1\u002Fruns\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>createdAt\u003C\u002Fcode>, \u003Ccode>status\u003C\u002Fcode>, \u003Ccode>finishedAt\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>taskId\u003C\u002Fcode>, \u003Ccode>taskVersionId\u003C\u002Fcode>, \u003Ccode>status\u003C\u002Fcode>; \u003Ccode>live=true\u003C\u002Fcode> for runs not yet finished, leaving out blocked and paused ones\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>GET \u002Fv1\u002Fexecutions\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>updatedAt\u003C\u002Fcode>, \u003Ccode>startedAt\u003C\u002Fcode>, \u003Ccode>runCount\u003C\u002Fcode>, \u003Ccode>costUsdCents\u003C\u002Fcode>, \u003Ccode>status\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>teamId\u003C\u002Fcode>, \u003Ccode>memberId\u003C\u002Fcode>, \u003Ccode>status\u003C\u002Fcode>; \u003Ccode>closed=true\u003C\u002Fcode> for closed ones only\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>GET \u002Fv1\u002Fwebhooks\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>receivedAt\u003C\u002Fcode>, \u003Ccode>status\u003C\u002Fcode>, \u003Ccode>target\u003C\u002Fcode>, \u003Ccode>responseStatus\u003C\u002Fcode>, \u003Ccode>durationMs\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>status\u003C\u002Fcode>, \u003Ccode>kind\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>GET \u002Fv1\u002Fcatalog\u002Ftasks\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>slug\u003C\u002Fcode>, \u003Ccode>name\u003C\u002Fcode>, \u003Ccode>status\u003C\u002Fcode>, \u003Ccode>provenance\u003C\u002Fcode>, \u003Ccode>createdAt\u003C\u002Fcode>, \u003Ccode>updatedAt\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>status\u003C\u002Fcode>, \u003Ccode>kind\u003C\u002Fcode>, \u003Ccode>provenance\u003C\u002Fcode>, \u003Ccode>origin\u003C\u002Fcode>; \u003Ccode>slug\u003C\u002Fcode> matches any part\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>GET \u002Fv1\u002Fcatalog\u002Froles\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>slug\u003C\u002Fcode>, \u003Ccode>name\u003C\u002Fcode>, \u003Ccode>provenance\u003C\u002Fcode>, \u003Ccode>createdAt\u003C\u002Fcode>, \u003Ccode>updatedAt\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>provenance\u003C\u002Fcode>; \u003Ccode>slug\u003C\u002Fcode> matches any part\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>GET \u002Fv1\u002Fmembers\u002F{id}\u002Femails\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Always by when it arrived; \u003Ccode>order\u003C\u002Fcode> still applies\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>unreadOnly=true\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>A page comes back in an envelope:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{ &quot;data&quot;: [{ &quot;id&quot;: &quot;…&quot; }], &quot;total&quot;: 142, &quot;page&quot;: 2, &quot;limit&quot;: 25, &quot;pages&quot;: 6 }\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Ccode>total\u003C\u002Fcode> counts every match, not only this page. \u003Ccode>pages\u003C\u002Fcode> is at least 1, even when there is nothing to list, and a\npage past the last has an empty \u003Ccode>data\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cp>Every other list answers with a plain array: teams, members, roles, gates, chats, recommendations, and tool\nconnections. The memory lists (\u003Ccode>GET \u002Fv1\u002Fmemory\u003C\u002Fcode> and the review queue) take \u003Ccode>page\u003C\u002Fcode> and \u003Ccode>limit\u003C\u002Fcode> too, and answer in a\nshape of their own. \u003Ccode>GET \u002Fv1\u002Fblockers\u003C\u002Fcode> pages its failed runs with \u003Ccode>failedPage\u003C\u002Fcode> and \u003Ccode>pageSize\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Ch2 id=\"safe-retries\">Safe retries\u003C\u002Fh2>\n\u003Cp>When a call times out or its connection drops, you do not know whether it happened. What to do next depends on the\ncall.\u003C\u002Fp>\n\u003Ch3 id=\"reads\">Reads\u003C\u002Fh3>\n\u003Cp>A \u003Ccode>GET\u003C\u002Fcode> changes nothing, so repeat it as often as you like. Two reads also mark something read: opening a chat, and\nopening a member's email.\u003C\u002Fp>\n\u003Ch3 id=\"writes-that-take-an-idempotency-key\">Writes that take an idempotency key\u003C\u002Fh3>\n\u003Cp>Two writes take an \u003Ccode>idempotencyKey\u003C\u002Fcode> in the body. No route reads an \u003Ccode>Idempotency-Key\u003C\u002Fcode> header.\u003C\u002Fp>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Call\u003C\u002Fth>\n\u003Cth>The key\u003C\u002Fth>\n\u003Cth>Sent again with the same key\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>POST \u002Fv1\u002Frecommendations\u002F{id}\u002Fdecision\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Optional, up to 80 characters.\u003C\u002Ftd>\n\u003Ctd>From the same caller, returns the decision the first call made instead of \u003Ccode>409 already_decided\u003C\u002Fcode>, and starts no second run.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>POST \u002Fv1\u002Fwebhooks\u002F{id}\u002Freplay\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Required: 16 to 64 letters, digits, \u003Ccode>-\u003C\u002Fcode> or \u003Ccode>_\u003C\u002Fcode>. A UUID will do.\u003C\u002Ftd>\n\u003Ctd>Returns the replay the first call made, instead of replaying again. The same key on a different delivery is a \u003Ccode>409\u003C\u002Fcode> (\u003Ccode>replay_idempotency_key_reused\u003C\u002Fcode>).\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Use a new key for each thing you mean to do, and the same key only when you are repeating it.\u003C\u002Fp>\n\u003Ch3 id=\"writes-that-are-safe-to-repeat\">Writes that are safe to repeat\u003C\u002Fh3>\n\u003Cul>\n\u003Cli>Setting something to a value: a \u003Ccode>PUT\u003C\u002Fcode> or \u003Ccode>PATCH\u003C\u002Fcode>, such as a task's schedule, a spend cap, or the enterprise's\nconcurrent-run limit, leaves it at that value however many times you send it.\u003C\u002Fli>\n\u003Cli>Clearing a run from Blockers, closing an execution, and reopening one: done twice, the second changes nothing and\nanswers as the first did.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch3 id=\"writes-that-refuse-a-repeat\">Writes that refuse a repeat\u003C\u002Fh3>\n\u003Cp>The second call is refused with a \u003Ccode>409\u003C\u002Fcode>, which usually means the first one went through:\u003C\u002Fp>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Call\u003C\u002Fth>\n\u003Cth>The repeat is refused with\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>Creating a task, team or role\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>task_slug_taken\u003C\u002Fcode>, \u003Ccode>team_slug_taken\u003C\u002Fcode>, \u003Ccode>role_slug_taken\u003C\u002Fcode>. The slug is made from the name when you send none, so the same body twice is refused, not made twice.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>Creating a member\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>member_email_taken\u003C\u002Fcode>. The email is always made from the name, so the same name twice is refused, not made twice. To add a second member with the same name, give them a name that tells them apart.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>Deciding a gate\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>gate_not_waiting\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>Cancelling a run\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>illegal_transition\u003C\u002Fcode>, once it has finished\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>Saving a task version with \u003Ccode>baseVersionId\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>task_version_conflict\u003C\u002Fcode>: the version your first call saved is now the current one. Read the task: if its current version is yours, the save landed. See \u003Ca href=\"\u002Fdocs\u002Fapi\u002Ftasks\">Tasks over the API\u003C\u002Fa>.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Ch3 id=\"writes-to-check-before-you-repeat\">Writes to check before you repeat\u003C\u002Fh3>\n\u003Cul>\n\u003Cli>\u003Cstrong>Starting a run.\u003C\u002Fstrong> While the first run is under way, a repeat is refused (\u003Ccode>409 overlap\u003C\u002Fcode>). Once it is waiting\nat a gate, blocked, or finished, a repeat starts a second run. Look for the first before you send another:\n\u003Ccode>GET \u002Fv1\u002Fruns?taskId=…\u003C\u002Fcode> lists the task's runs, newest first.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Retrying a run.\u003C\u002Fstrong> A repeat returns the same new run only while that run is still \u003Ccode>pending\u003C\u002Fcode>. Once it has\nstarted, a repeat makes another. Each retry's input carries the run it retried as \u003Ccode>priorRunId\u003C\u002Fcode>, so the runs list\nshows whether yours landed.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Sending a chat message.\u003C\u002Fstrong> A repeat sends the message again, unless the member is still replying to the first\n(\u003Ccode>409 chat_replying\u003C\u002Fcode>).\u003C\u002Fli>\n\u003C\u002Ful>\n",[12,16,19,22,25,28,32,35,38,41],{"id":13,"text":14,"level":15,"planned":8},"requests","Requests",2,{"id":17,"text":18,"level":15,"planned":8},"responses","Responses",{"id":20,"text":21,"level":15,"planned":8},"errors","Errors",{"id":23,"text":24,"level":15,"planned":8},"pagination","Pagination",{"id":26,"text":27,"level":15,"planned":8},"safe-retries","Safe retries",{"id":29,"text":30,"level":31,"planned":8},"reads","Reads",3,{"id":33,"text":34,"level":31,"planned":8},"writes-that-take-an-idempotency-key","Writes that take an idempotency key",{"id":36,"text":37,"level":31,"planned":8},"writes-that-are-safe-to-repeat","Writes that are safe to repeat",{"id":39,"text":40,"level":31,"planned":8},"writes-that-refuse-a-repeat","Writes that refuse a repeat",{"id":42,"text":43,"level":31,"planned":8},"writes-to-check-before-you-repeat","Writes to check before you repeat",1791124519333]