Riferimento API
Motir API · version 1.64.0 · 70 operations · /api/openapi/v1.json
Ogni operazione servita dall’API, generata dal documento OpenAPI pubblicato da Motir e recuperata quando viene richiesta questa pagina: è quindi esattamente ciò che il server offre in questo momento. Per ciascuna trovi i parametri, il corpo che accetta, una richiesta da copiare e ogni stato con cui può rispondere.
Open a dispatch run, with the SET of cards it owns
OPEN one run of a Motir CLI command and record THE SET it owns: every card, in the run’s own order, including the ones it has already decided to SKIP, each with its reason. ⚠️ THE SET IS SETTLED HERE BECAUSE THIS IS THE ONE MOMENT IT EXISTS — a scope claim has just returned its members, or a batch snapshot has just been frozen. Rebuilt afterwards from per-card events it becomes a list of what the run got round to, and the skipped cards vanish entirely. A single-card `motir next` is the degenerate case: one card in the set. IDEMPOTENT on `idempotencyKey`: a repeat returns the EXISTING run with `created: false` rather than forking history, so a retry after a timeout is safe. It records the run and NOTHING ELSE: it moves no work-item status, writes no pull request and holds no cost. Requires the `work_item:edit` permission.
Corpo della richiesta
The command, its origin, the agent and model, an optional scope, an optional idempotency key, and the ordered SET of cards.
| Property | Type | Description |
|---|---|---|
| projectKey required | string | |
| command required | string next · run · run_scope · batch · auto · fix · continue · review | |
| origin optional | string local · hosted | |
| scopeKey optional | string | |
| scopeLabel optional | string | |
| agent optional | string | |
| model optional | string | |
| idempotencyKey optional | string | |
| cards optional | object[] |
Esempio
curl
curl -X POST 'https://app.motir.co/api/v1/dispatch-runs' \
-H 'Authorization: Bearer $MOTIR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"projectKey":"<projectKey>","command":"next"}'Risposte
| Stato | Condizione |
|---|---|
| 201 | The run with its set and its resume cursor, and whether this call created it. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 409 | The request conflicts with existing state. The body is well-formed; the state is not what the request assumed. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| run required | object | |
| created required | boolean |
Get a dispatch run, with the SET of cards it owns
READ one run and its set: every card in the run’s own order with its current disposition, and the stream’s cursor. It is how a CLI that did not open a run — a hosted run the SERVER opened, which the `motir` CLI in its container then adopts (`docs/decisions/hosted-run-runs-the-cli-as-the-app.md` §3) — learns the cards it owns and their order, instead of claiming a second set. A hosted run’s own credential may read its own run and no other. A read: it writes nothing and moves no status. Requires the `project:browse` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| id string · required | path | The dispatch run’s id. |
Esempio
curl
curl -X GET 'https://app.motir.co/api/v1/dispatch-runs/{id}' \
-H 'Authorization: Bearer $MOTIR_TOKEN'Risposte
| Stato | Condizione |
|---|---|
| 200 | The run with its set and its resume cursor. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| id required | string | |
| projectId required | string | |
| command required | string next · run · run_scope · batch · auto · fix · continue · review | |
| origin required | string local · hosted · instance | |
| scopeWorkItemId required | string | null | |
| scopeLabel required | string | null | |
| status required | string running · succeeded · failed · cancelled · timed_out | |
| stopReason required | string | null drained · completed · max · halted · interrupted · replanned · gated · abandoned | |
| agent required | string | null | |
| model required | string | null | |
| startedAt required | string (date-time) | |
| endedAt required | string (date-time) | null | |
| lastHeartbeatAt required | string (date-time) | null | |
| createdById required | string | null | |
| agentInstance required | object | null | |
| cards required | object[] | |
| seq required | integer | |
| continues optional | object | null | |
| repair optional | object | null |
Close a dispatch run with its stop reason
CLOSE the run: its terminal status, its `stopReason`, and every leg still unsettled — a `queued` card becomes `not_reached` (the run never got to it) and a `running` one becomes `failed` (the run ended while an agent was on it and nothing reported an outcome). Omit `status` and it is DERIVED from the stop reason, which is the mapping every caller would otherwise re-implement differently: `halted` is the only failure, `interrupted` is `cancelled`, and `replanned` is a SUCCESS — an agent that refused a card and submitted a plan exited 0. Guarded by a row lock and a re-read, so a close racing the server’s own abandoned-run reap cannot overwrite an already-terminal answer; the loser is a conflict. It writes NO work-item status — the CLI owns every transition. Requires the `work_item:edit` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| id string · required | path | The dispatch run’s id. |
Corpo della richiesta
The stop reason, and optionally an explicit terminal status.
| Property | Type | Description |
|---|---|---|
| stopReason required | string drained · completed · max · halted · interrupted · replanned · gated · abandoned | |
| status optional | string succeeded · failed · cancelled · timed_out |
Esempio
curl
curl -X POST 'https://app.motir.co/api/v1/dispatch-runs/{id}/close' \
-H 'Authorization: Bearer $MOTIR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"stopReason":"drained"}'Risposte
| Stato | Condizione |
|---|---|
| 200 | The closed run with its settled set. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 409 | The request conflicts with existing state. The body is well-formed; the state is not what the request assumed. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| id required | string | |
| projectId required | string | |
| command required | string next · run · run_scope · batch · auto · fix · continue · review | |
| origin required | string local · hosted · instance | |
| scopeWorkItemId required | string | null | |
| scopeLabel required | string | null | |
| status required | string running · succeeded · failed · cancelled · timed_out | |
| stopReason required | string | null drained · completed · max · halted · interrupted · replanned · gated · abandoned | |
| agent required | string | null | |
| model required | string | null | |
| startedAt required | string (date-time) | |
| endedAt required | string (date-time) | null | |
| lastHeartbeatAt required | string (date-time) | null | |
| createdById required | string | null | |
| agentInstance required | object | null | |
| cards required | object[] | |
| seq required | integer | |
| continues optional | object | null | |
| repair optional | object | null |
Get the prompt that writes a run’s How to test onto its run target
The CLOSE-OUT prompt for a run launched against a work item (a scoped run): the text a CLI hands ONE agent after the run’s last card lands and BEFORE it marks the run’s pull requests ready, so HOW TO TEST is written once onto the run target by an agent that sees every card the run landed. The target and the landed cards are read from the run’s own record — the caller names only the run. A run with no scope (an unscoped batch, whose every card was its own target) is refused with `NO_RUN_TARGET` rather than answered with a defaulted target. A read: it writes nothing and moves no status. Requires the `project:browse` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| id string · required | path | The dispatch run’s id, as `openDispatchRun` returned it. |
Esempio
curl
curl -X GET 'https://app.motir.co/api/v1/dispatch-runs/{id}/close-out-prompt' \
-H 'Authorization: Bearer $MOTIR_TOKEN'Risposte
| Stato | Condizione |
|---|---|
| 200 | The run target, the landed cards, and the prompt text. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| runId required | string | |
| targetKey required | string | |
| prompt required | string | |
| landedKeys required | string[] |
Append a batch of events to a dispatch run
APPEND events to a run’s ordered stream. Each is RUN-scoped, or CARD-scoped by naming a `workItemKey` the run already owns — an event never ADDS a card to a run’s set, because the set is the plan the run published. The server assigns each event its `seq` inside ONE transaction, so a batch’s order survives and the response’s `seq` is the cursor for the next append. Batch them: a chatty agent must not cost one request per line. An event may also carry the leg’s new `disposition` (plus `sessionBranch` / `exitCode`), applied to that card in the SAME transaction — so a viewer sees a card go `implemented` at the moment it happens rather than at close. `body` is the OPT-IN log payload: send it only when the operator asked for it. It is REFUSED above 16 KiB rather than truncated, and it is cleared 30 days after the event. Appending to a run that has already closed is a conflict, not a silent no-op. Requires the `work_item:edit` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| id string · required | path | The dispatch run’s id, as `openDispatchRun` returned it. |
Corpo della richiesta
Up to 200 events, in the order they happened.
| Property | Type | Description |
|---|---|---|
| events required | object[] |
Esempio
curl
curl -X POST 'https://app.motir.co/api/v1/dispatch-runs/{id}/events' \
-H 'Authorization: Bearer $MOTIR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"events":[]}'Risposte
| Stato | Condizione |
|---|---|
| 200 | How many events were written, the new cursor, and every leg this batch moved. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 409 | The request conflicts with existing state. The body is well-formed; the state is not what the request assumed. |
| 413 | The uploaded file is larger than the per-file limit this organization’s plan allows. Note the SEPARATE platform ceiling on a direct upload, documented on the operation itself (docs/decisions/attachment-api-door.md §1). |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| runId required | string | |
| appended required | integer | |
| seq required | integer | |
| cards required | object[] |
Get a running hosted run’s git credentials
A running HOSTED run trades its own run credential for fresh git credentials: one entry per repository of the run, each an installation token of the Motir GitHub App that writes that repository, narrowed to the run’s repositories and to contents + pull-request write. Repositories in one installation share a token. Each lives one hour, so the run asks again whenever it needs one — this is also its FIRST git credential. Every token handed out is recorded against the run and revoked when the run ends. The author is the App’s bot, never a person; `dispatchedBy` names the dispatcher for pull-request bodies. Only the run’s own credential is answered — any other token, a person’s included, is refused before the run is read. Requires the `work_item:edit` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| id string · required | path | The hosted run’s id — the run its credential is bound to. |
Esempio
curl
curl -X POST 'https://app.motir.co/api/v1/dispatch-runs/{id}/git-credential' \
-H 'Authorization: Bearer $MOTIR_TOKEN'Risposte
| Stato | Condizione |
|---|---|
| 200 | One credential per repository of the run, and who dispatched it. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 409 | The request conflicts with existing state. The body is well-formed; the state is not what the request assumed. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
| 503 | A dependency this operation needs — the motir-ai planning service — could not be reached or is misconfigured. The request itself was fine; retrying later is the right response. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| credentials required | object[] | |
| dispatchedBy required | string | null |
Report that a local dispatch run is still alive
A LOCAL run says it is still working. The CLI sends one every 60 seconds while a run is open; a local run silent for 5 minutes is DEAD — its card shows the run died and `motir continue` may take the work over — and the server closes it `abandoned`. The server stamps the time itself; the request carries no body. Only the operator who opened the run may beat for it. A run that is already closed — usually by that lapse — is a conflict, and the answer is to stop beating, not to retry. It writes NO work-item status and no event. Requires the `work_item:edit` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| id string · required | path | The dispatch run’s id, as `openDispatchRun` returned it. |
Esempio
curl
curl -X POST 'https://app.motir.co/api/v1/dispatch-runs/{id}/heartbeat' \
-H 'Authorization: Bearer $MOTIR_TOKEN'Risposte
| Stato | Condizione |
|---|---|
| 204 | The heartbeat was recorded. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 409 | The request conflicts with existing state. The body is well-formed; the state is not what the request assumed. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Delete a folder, moving its contents up
Delete a folder. NOTHING inside it is deleted: its child folders and filed work items move to the folder’s own parent (or the project root), and the body names them. Refused whole, changing nothing, when a child folder’s name collides at the destination (`FOLDER_NAME_TAKEN`) or when a ROOT folder holds a subtask that would be left with neither a parent nor a folder (`SUBTASK_NEEDS_PLACEMENT`). Requires the `work_item:edit` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| folderId string · required | path | The folder’s id. A folder has no `MOTIR-<n>` key, so its id is its name on the wire. |
Esempio
curl
curl -X DELETE 'https://app.motir.co/api/v1/folders/{folderId}' \
-H 'Authorization: Bearer $MOTIR_TOKEN'Risposte
| Stato | Condizione |
|---|---|
| 200 | What the delete moved, and where. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 409 | The request conflicts with existing state. The body is well-formed; the state is not what the request assumed. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| deletedFolderId required | string | |
| destinationFolderId required | string | null | |
| movedFolderIds required | string[] | |
| movedWorkItemIds required | string[] |
Read a folder
One folder by id, with its project key and path. A folder in another workspace, one in a project this token is not bound to, and one that never existed are the same 404 — the existence-oracle rule. Requires the `project:browse` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| folderId string · required | path | The folder’s id. A folder has no `MOTIR-<n>` key, so its id is its name on the wire. |
Esempio
curl
curl -X GET 'https://app.motir.co/api/v1/folders/{folderId}' \
-H 'Authorization: Bearer $MOTIR_TOKEN'Risposte
| Stato | Condizione |
|---|---|
| 200 | The folder. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| id required | string | |
| projectKey required | string | |
| parentFolderId required | string | null | |
| name required | string | |
| path required | string[] | |
| position required | string | |
| createdAt required | string (date-time) | |
| updatedAt required | string (date-time) |
Rename, move or reorder a folder
A RENAME (`name`) OR a PLACEMENT (`parentFolderId`, `beforeId`, `afterId`) — never both in one request, because the two are applied separately and a combined request could half-apply; sending both is a 422 `INVALID_REQUEST`. A placement with no `parentFolderId` keeps the current parent (a pure reorder). Moving a folder into itself or one of its own folders is `FOLDER_CYCLE`; a destination in another project is `CROSS_PROJECT_FOLDER`; a name clash at the destination is `FOLDER_NAME_TAKEN`. There is no `If-Match`: folders carry no concurrency token. Requires the `work_item:edit` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| folderId string · required | path | The folder’s id. A folder has no `MOTIR-<n>` key, so its id is its name on the wire. |
Corpo della richiesta
Either the new name, or the new placement.
| Property | Type | Description |
|---|---|---|
| name optional | string | RENAME the folder. Not combinable with a placement. |
| parentFolderId optional | string | null | MOVE the folder into this folder, or `null` for the project root. Omit to keep its parent (a pure reorder). |
| beforeId optional | string | null | Place it AFTER this sibling folder (the one that sorts before it). |
| afterId optional | string | null | Place it BEFORE this sibling folder (the one that sorts after it). |
Esempio
curl
curl -X PATCH 'https://app.motir.co/api/v1/folders/{folderId}' \
-H 'Authorization: Bearer $MOTIR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{}'Risposte
| Stato | Condizione |
|---|---|
| 200 | The folder after the change. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 409 | The request conflicts with existing state. The body is well-formed; the state is not what the request assumed. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| id required | string | |
| projectKey required | string | |
| parentFolderId required | string | null | |
| name required | string | |
| path required | string[] | |
| position required | string | |
| createdAt required | string (date-time) | |
| updatedAt required | string (date-time) |
Who this token is
The token owner, the workspace the token is bound to, and the scopes it was granted. Call this first: the scope list is how a client discovers what its own credential may do without probing endpoints and collecting 403s. Requires the `project:browse` permission.
Esempio
curl
curl -X GET 'https://app.motir.co/api/v1/me' \ -H 'Authorization: Bearer $MOTIR_TOKEN'
Risposte
| Stato | Condizione |
|---|---|
| 200 | The token’s identity and granted scopes. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| user required | object | |
| workspaceId required | string | |
| permissions required | string[] |
Read a plan with the proposals it bundles
Read a plan WITH its proposals — what a planning pass actually proposed, not just how many. Each proposal carries its `op` (`add` / `modify` / `remove`), the `proposedFields` of an `add`, the `patch` of a `modify`, and the `parentRef` / `blockedByRefs` that let you rebuild the proposed tree and its dependency edges. ⚠️ These are PROPOSALS, not work items: an `add`’s `workItemKey` is `null` and stays null until the plan is approved in Motir, which is the only path from a proposal to a work item. A plan still generating returns the proposals that have arrived so far. Requires the `project:browse` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| planId string · required | path | The plan id. |
Esempio
curl
curl -X GET 'https://app.motir.co/api/v1/plans/{planId}' \
-H 'Authorization: Bearer $MOTIR_TOKEN'Risposte
| Stato | Condizione |
|---|---|
| 200 | The plan and its proposals. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| id required | string | |
| status required | string generating · planned · stale · approved · declined | |
| origin required | string user · cadence | |
| title required | string | null | |
| summary required | string | null | |
| sourceJobId required | string | null | |
| proposalCount required | integer | |
| createdAt required | string | |
| plannedAt required | string | null | |
| decidedAt required | string | null | |
| proposals required | object[] |
Read what became of a submitted planning job
Read a plan’s status (`generating` / `planned` / `approved` / `declined`), how many PROPOSALS it bundles, and — while it is still generating — whether the producing job is alive or already FAILED. That last distinction is the point of this endpoint: a failed job writes no terminal plan state of its own — a background reconciler declines an empty one within the hour, so the plan status alone cannot tell you to stop polling NOW. `job.reachable: false` means motir-ai could not be asked, not that the job died. A pure read; the proposal count is NOT a count of created work items. Requires the `project:browse` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| planId string · required | path | The plan id an expansion or plan-session submit returned. |
Esempio
curl
curl -X GET 'https://app.motir.co/api/v1/plans/{planId}/status' \
-H 'Authorization: Bearer $MOTIR_TOKEN'Risposte
| Stato | Condizione |
|---|---|
| 200 | The plan’s status, its proposal count, and the job’s liveness. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| planId required | string | |
| status required | string generating · planned · stale · approved · declined | |
| origin required | string user · cadence | |
| jobId required | string | null | |
| proposalCount required | integer | |
| createdAt required | string | |
| plannedAt required | string | null | |
| decidedAt required | string | null | |
| job required | object | null |
List the projects in this token’s workspace
Every project the token owner may browse in the bound workspace, ordered by key ascending — a total order the page addressing owns, so a cursor can never skip or duplicate a row. A token BOUND to one project lists exactly that project — the same set `getProject` lets it open — so a narrowed credential is never shown a project it would then be refused. Requires the `project:browse` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| cursor string · optional | query | An opaque page cursor from a previous response’s `nextCursor`. Omit for the first page. Cursors are signed and scoped to their own collection — one issued elsewhere is a 422, never a silent reset. |
| limit integer · optional | query | Rows per page. Defaults to 50; a larger value is CLAMPED to 100 rather than rejected. |
Esempio
curl
curl -X GET 'https://app.motir.co/api/v1/projects' \ -H 'Authorization: Bearer $MOTIR_TOKEN'
Risposte
| Stato | Condizione |
|---|---|
| 200 | A page of projects. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
PageEnvelope & object
Read a project
One project by key. A project the caller may not browse answers 404, not 403 — a 403 would confirm the project exists and let a caller enumerate which keys are real. Requires the `project:browse` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| projectKey string · required | path | The project’s key — the prefix of its work items’ keys, e.g. `MOTIR`. |
Esempio
curl
curl -X GET 'https://app.motir.co/api/v1/projects/{projectKey}' \
-H 'Authorization: Bearer $MOTIR_TOKEN'Risposte
| Stato | Condizione |
|---|---|
| 200 | The project. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| key required | string | |
| name required | string | |
| accessMode required | string workspace · members · public | Who may enter the project — the authoritative access field. |
| accessLevel required | string open · limited · private · public | DEPRECATED — derived from `accessMode` (workspace → open, members → private, public → public), so `limited` is never emitted. Read `accessMode`. |
| archived required | boolean |
Read a project’s backlog
The to-be-planned pile, in backlog-rank order. ⚠️ Done-category items are EXCLUDED — a finished unsprinted item does not belong in the backlog. (A sprint’s members are deliberately NOT filtered that way; see `listSprintWorkItems`.) Reports a total, because the read behind it already computes one. Requires the `project:browse` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| projectKey string · required | path | The project’s key — the prefix of its work items’ keys, e.g. `MOTIR`. |
| cursor string · optional | query | An opaque page cursor from a previous response’s `nextCursor`. Omit for the first page. Cursors are signed and scoped to their own collection — one issued elsewhere is a 422, never a silent reset. |
| limit integer · optional | query | Rows per page. Defaults to 50; a larger value is CLAMPED to 100 rather than rejected. |
| filter string · optional | query | A serialised filter expression, in the same grammar the product’s own list views use — never an ad-hoc `?status=&assignee=` axis. An unknown field, operator or value is a 422 naming which. |
Esempio
curl
curl -X GET 'https://app.motir.co/api/v1/projects/{projectKey}/backlog' \
-H 'Authorization: Bearer $MOTIR_TOKEN'Risposte
| Stato | Condizione |
|---|---|
| 200 | A page of backlog items, with the total behind it. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
RankedPageEnvelope & object
Move work items out of their sprint and back to the backlog
An atomic batch move. An EMPTY array is a deliberate 200 no-op, not an error: a script that computed an empty batch has nothing to do rather than a mistake to fix. An over-cap batch is refused WHOLE, never partially applied. Requires the `sprint:manage` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| projectKey string · required | path | The project’s key — the prefix of its work items’ keys, e.g. `MOTIR`. |
Corpo della richiesta
The work items to move.
| Property | Type | Description |
|---|---|---|
| workItemKeys required | string[] |
Esempio
curl
curl -X POST 'https://app.motir.co/api/v1/projects/{projectKey}/backlog/work-items' \
-H 'Authorization: Bearer $MOTIR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"workItemKeys":[]}'Risposte
| Stato | Condizione |
|---|---|
| 200 | The keys that moved, in request order. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| movedKeys required | string[] |
Browse a project’s approved designs
A cursor-paged collection of the project’s APPROVED designs, newest first — what an agent browses when the design it was handed is not the one it needs. A design still under review is not listed: it is not something to build against. ⚠️ NO download links: a list would mint one presign per row, and every one of them would start expiring before the caller read the page. Take links from `GET /api/v1/work-items/{key}/design` on the design you actually want. Requires the `project:browse` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| projectKey string · required | path | The project’s key — the prefix of its work items’ keys, e.g. `MOTIR`. |
| cursor string · optional | query | An opaque page cursor from a previous response’s `nextCursor`. Omit for the first page. A cursor is signed and scoped to its collection — one issued elsewhere is a 422, never a silent reset. |
| limit integer · optional | query | Rows per page. Defaults to 50; a larger value is CLAMPED to 100 rather than rejected. |
| pathPrefix string · optional | query | Return only designs holding an asset whose repository `sourcePath` starts with this prefix — how a delta mock’s amended base is found (AMENDMENT 5 Q6). |
| query string · optional | query | A case-insensitive substring of the design card’s title. |
Esempio
curl
curl -X GET 'https://app.motir.co/api/v1/projects/{projectKey}/designs' \
-H 'Authorization: Bearer $MOTIR_TOKEN'Risposte
| Stato | Condizione |
|---|---|
| 200 | A page of approved designs. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
PageEnvelope & object
List one level of a project’s folders
The CHILD folders of one level, in the order the tree shows them — the project root by default, or `parentFolderId`’s children. The tree is read level by level: walk down by listing a folder’s children. Each row carries its `path`, root first. A `parentFolderId` that is not a folder of this project is `FOLDER_NOT_FOUND`. Requires the `project:browse` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| projectKey string · required | path | The project’s key — the prefix of its work items’ keys, e.g. `MOTIR`. |
| parentFolderId string · optional | query | List this folder’s child folders. Omit (or send empty) for the project root. |
| cursor string · optional | query | An opaque page cursor from a previous response’s `nextCursor`. Omit for the first page. Cursors are signed and scoped to their own collection — one issued elsewhere is a 422, never a silent reset. |
| limit integer · optional | query | Rows per page. Defaults to 50; a larger value is CLAMPED to 100 rather than rejected. |
Esempio
curl
curl -X GET 'https://app.motir.co/api/v1/projects/{projectKey}/folders' \
-H 'Authorization: Bearer $MOTIR_TOKEN'Risposte
| Stato | Condizione |
|---|---|
| 200 | A page of folders at that level. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
PageEnvelope & object
Create a folder
Create a folder at the project root or inside `parentFolderId`, appended last among its siblings. A sibling already holding the name (case-insensitively) is `FOLDER_NAME_TAKEN` (409); an empty or over-long name is `INVALID_FOLDER_NAME`; a parent in another project is `CROSS_PROJECT_FOLDER`. The `Location` header names the created folder. Requires the `work_item:edit` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| projectKey string · required | path | The project’s key — the prefix of its work items’ keys, e.g. `MOTIR`. |
Corpo della richiesta
The folder to create.
| Property | Type | Description |
|---|---|---|
| name required | string | The folder's name. Trimmed; empty or over 120 characters is `INVALID_FOLDER_NAME`. |
| parentFolderId optional | string | null | The folder to create it inside. Omit or `null` for the project root. |
Esempio
curl
curl -X POST 'https://app.motir.co/api/v1/projects/{projectKey}/folders' \
-H 'Authorization: Bearer $MOTIR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"name":"<name>"}'Risposte
| Stato | Condizione |
|---|---|
| 201 | The created folder. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 409 | The request conflicts with existing state. The body is well-formed; the state is not what the request assumed. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| id required | string | |
| projectKey required | string | |
| parentFolderId required | string | null | |
| name required | string | |
| path required | string[] | |
| position required | string | |
| createdAt required | string (date-time) | |
| updatedAt required | string (date-time) |
Open or resume the planning conversation for a scope
Open — or RESUME — the planning conversation for a project, and read its thread. Changing a plan in Motir is a multi-turn CONVERSATION: add turns, then send the accumulated intent. There is ONE thread per project per anchor set, so calling this again returns the SAME conversation, with every turn already on it — including the one the Motir web app shows. Pass `targetKeys` to anchor the conversation at specific work items ("re-plan these two"); omit it for the project-wide thread. The anchor set is the thread’s identity: order and duplicates do not matter. Opening submits nothing and costs nothing — which is why it is `read`-scoped despite being a POST (a GET that creates a row would not be safe). Requires the `ai:plan` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| projectKey string · required | path | The project key, e.g. `MOTIR`. |
Corpo della richiesta
The optional anchor set. Omit for the project-wide thread.
| Property | Type | Description |
|---|---|---|
| targetKeys optional | string[] | |
| sessionId optional | string | The `id` of the planning session to address, as a previous call returned it. Omit to use your resumable session for the scope (or start one with a turn). |
Esempio
curl
curl -X POST 'https://app.motir.co/api/v1/projects/{projectKey}/plan-session' \
-H 'Authorization: Bearer $MOTIR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{}'Risposte
| Stato | Condizione |
|---|---|
| 200 | The thread, with every turn on it. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| id required | string | |
| targetKeys required | string[] | |
| turnCount required | integer | |
| lastJobId required | string | null | |
| lastSubmittedAt required | string | null | |
| createdAt required | string | |
| updatedAt required | string | |
| turns required | object[] |
Send the thread’s accumulated intent to the planner
Send this conversation’s accumulated intent to the planner: every turn on the thread, in order, as ONE change. Returns `202` with `{ jobId, planId, statusUrl }` the moment the job is accepted — it does not wait, and the body carries no result because there is none yet. The thread stays INTACT and can be refined with another turn. ⚠️ This is the act that SPENDS the token owner’s AI credits, and it produces a PLAN of proposals: approving that plan in Motir is the only thing that turns a proposal into a work item. Submitting a thread with no turns is refused. Requires the `ai:plan` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| projectKey string · required | path | The project key, e.g. `MOTIR`. |
Corpo della richiesta
The optional anchor set naming which thread to submit.
| Property | Type | Description |
|---|---|---|
| targetKeys optional | string[] | |
| sessionId optional | string | The `id` of the planning session to address, as a previous call returned it. Omit to use your resumable session for the scope (or start one with a turn). |
Esempio
curl
curl -X POST 'https://app.motir.co/api/v1/projects/{projectKey}/plan-session/submissions' \
-H 'Authorization: Bearer $MOTIR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{}'Risposte
| Stato | Condizione |
|---|---|
| 202 | The job was accepted. Nothing has been planned yet — poll `statusUrl` for the outcome. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 402 | A plan entitlement is exhausted — the workspace owner’s AI credits, or the organization’s total attachment-storage cap. The request was valid; it was refused for want of headroom, and retrying will not help until the limit is lifted. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
| 503 | A dependency this operation needs — the motir-ai planning service — could not be reached or is misconfigured. The request itself was fine; retrying later is the right response. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| jobId required | string | |
| planId required | string | |
| statusUrl required | string |
Add one turn to the planning conversation
Add ONE turn — what you want changed about the plan. ⚠️ IMPORTANT: appending does NOT submit. The turn is persisted immediately, but no job starts, no credits are spent and no work item changes; turns ACCUMULATE until you post a submission, which is what sends them to the planner. That separation is the point — a later turn REFINES the earlier ones rather than replacing them, so "add auth to the billing epic" then "keep them under 3 points" go out as ONE coherent change. Addresses the thread by scope, so it always extends the same conversation. Requires the `ai:plan` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| projectKey string · required | path | The project key, e.g. `MOTIR`. |
Corpo della richiesta
What to say in this turn, and the optional anchor set it belongs to.
| Property | Type | Description |
|---|---|---|
| targetKeys optional | string[] | |
| sessionId optional | string | The `id` of the planning session to address, as a previous call returned it. Omit to use your resumable session for the scope (or start one with a turn). |
| body required | string |
Esempio
curl
curl -X POST 'https://app.motir.co/api/v1/projects/{projectKey}/plan-session/turns' \
-H 'Authorization: Bearer $MOTIR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"body":"<body>"}'Risposte
| Stato | Condizione |
|---|---|
| 200 | The thread, with the new turn appended. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 409 | The request conflicts with existing state. The body is well-formed; the state is not what the request assumed. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| id required | string | |
| targetKeys required | string[] | |
| turnCount required | integer | |
| lastJobId required | string | null | |
| lastSubmittedAt required | string | null | |
| createdAt required | string | |
| updatedAt required | string | |
| turns required | object[] |
Read a project’s ready BUGS lane
The ready BUG WORK: a ready `bug` (its own group, `container: null`), and the ready subtasks of a bug (grouped under it, `container` = the bug). The same order, facets and cascade as `getProjectReadyLeaves`; the two lanes are disjoint and together hold the whole ready set. Requires the `project:browse` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| projectKey string · required | path | The project’s key — the prefix of its work items’ keys, e.g. `MOTIR`. |
| cursor string · optional | query | An opaque page cursor from a previous response’s `nextCursor`. Omit for the first page. Cursors are signed and scoped to their own collection — one issued elsewhere is a 422, never a silent reset. |
| limit integer · optional | query | Rows per page. Defaults to 50; a larger value is CLAMPED to 100 rather than rejected. |
| kind string[] · optional | query | Narrow to one or more work-item kinds, as `?kind=epic&kind=story`. An unknown kind is a 422. |
| priority string[] · optional | query | Narrow to one or more priorities, as `?priority=high&priority=urgent`. An unknown priority is a 422. |
| assigneeId string · optional | query | TRI-STATE, and all three are reachable: OMIT for any assignee, the literal `none` for the unassigned bucket, or a user id for that user's items. An empty value is treated as omitted. |
| ancestor string[] · optional | query | SCOPE the read to the ready leaves STRICTLY BENEATH one or more containers, at ANY depth, as `?ancestor=MOTIR-42&ancestor=MOTIR-43` — an any-of set, like `kind`. The named container is NOT in its own result, so a childless one returns an empty page rather than itself: that is the honest answer to “what is ready under this story” for a story nobody has decomposed. ⚠️ It NARROWS the same answer the unfaceted read gives and can never widen it — a leaf whose ancestor chain reaches the named container but is not itself all-ready stays absent, because the parent-ready cascade is computed first and this filters its result (with `allowSoftBlock=true` it narrows that widened answer in the same way). An unknown key, or one belonging to another project, is a 422 — indistinguishable from each other. |
| sprintId string · optional | query | SCOPE the read to the items whose OWN `sprintId` matches — a sprint id, or the reserved literal `active` for the project's active sprint. SINGLE-VALUED: membership is a scalar column, so there is no any-of question to ask. Membership is DIRECT and never inherited — an item under an in-sprint parent but not itself in the sprint is out of scope. A sprint that is not this project's, and `active` on a project between sprints, are both a 422 rather than a silently unfiltered page. An empty value is treated as omitted. |
| allowSoftBlock string · optional | query | WIDEN the read past a SOFT block. `true` keeps the requirement that every listed leaf’s OWN `blocked_by` dependencies are done, but no longer drops a leaf because an ANCESTOR is not ready — so a leaf held only by its epic’s or story’s block is listed, while a leaf with its own open blocker (a HARD block) never is. A container whose own blockers are open counts toward its children only as an ancestor. Composes with every other parameter, `ancestor` included. Absent, empty or `false` returns exactly the parent-ready cascade described above. Any other value is a 422. |
Esempio
curl
curl -X GET 'https://app.motir.co/api/v1/projects/{projectKey}/ready/bugs' \
-H 'Authorization: Bearer $MOTIR_TOKEN'Risposte
| Stato | Condizione |
|---|---|
| 200 | A page of ready bug work, grouped by bug, with its edges. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
PageEnvelope & object
Read a project’s ready CONTAINERS lane
The RUNNABLE CONTAINERS — a `story`, `task` or `bug` whose every child is childless — that hold at least one row of the leaves lane, bugs excluded: the units a parent run takes. An `epic` is never one, nor is a container holding a grandchild. In the leaves lane’s group order, so `items[0]` is the next parent run. `readyLeafCount` counts its leaves-lane rows; `childCount` every live child. The facets apply to the CONTAINER; `kind`, `ancestor` and `allowSoftBlock` do not apply here and are a 422. Requires the `project:browse` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| projectKey string · required | path | The project’s key — the prefix of its work items’ keys, e.g. `MOTIR`. |
| cursor string · optional | query | An opaque page cursor from a previous response’s `nextCursor`. Omit for the first page. Cursors are signed and scoped to their own collection — one issued elsewhere is a 422, never a silent reset. |
| limit integer · optional | query | Rows per page. Defaults to 50; a larger value is CLAMPED to 100 rather than rejected. |
| priority string[] · optional | query | Narrow to containers of one or more priorities — the CONTAINER’s own priority, as `?priority=high&priority=highest`. An unknown priority is a 422. |
| assigneeId string · optional | query | TRI-STATE, and all three are reachable: OMIT for any assignee, the literal `none` for the unassigned bucket, or a user id for that user's items. An empty value is treated as omitted. |
| sprintId string · optional | query | SCOPE the read to the items whose OWN `sprintId` matches — a sprint id, or the reserved literal `active` for the project's active sprint. SINGLE-VALUED: membership is a scalar column, so there is no any-of question to ask. Membership is DIRECT and never inherited — an item under an in-sprint parent but not itself in the sprint is out of scope. A sprint that is not this project's, and `active` on a project between sprints, are both a 422 rather than a silently unfiltered page. An empty value is treated as omitted. |
Esempio
curl
curl -X GET 'https://app.motir.co/api/v1/projects/{projectKey}/ready/containers' \
-H 'Authorization: Bearer $MOTIR_TOKEN'Risposte
| Stato | Condizione |
|---|---|
| 200 | A page of runnable containers holding ready leaves. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
PageEnvelope & object
Read a project’s ready LEAVES lane
The ready set minus BUG WORK (a `bug`, or a leaf whose parent is a `bug`), each row naming the RUNNABLE CONTAINER it groups under — a `story`, `task` or `bug` whose every child is childless, the shape a parent run accepts — or `null`. ORDER is part of the contract: rows are grouped by `container ?? self`, a group ranks by its best member’s `(kind, priority, key)`, and members keep that rank inside it, so `items[0]` is the next leaf to run. Readiness is the parent-ready cascade — a childless leaf, not terminal, every `blocked_by` blocker terminal and every ancestor ready; this lane and `getProjectReadyBugs` are disjoint and together hold the whole ready set. Requires the `project:browse` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| projectKey string · required | path | The project’s key — the prefix of its work items’ keys, e.g. `MOTIR`. |
| cursor string · optional | query | An opaque page cursor from a previous response’s `nextCursor`. Omit for the first page. Cursors are signed and scoped to their own collection — one issued elsewhere is a 422, never a silent reset. |
| limit integer · optional | query | Rows per page. Defaults to 50; a larger value is CLAMPED to 100 rather than rejected. |
| kind string[] · optional | query | Narrow to one or more work-item kinds, as `?kind=epic&kind=story`. An unknown kind is a 422. |
| priority string[] · optional | query | Narrow to one or more priorities, as `?priority=high&priority=urgent`. An unknown priority is a 422. |
| assigneeId string · optional | query | TRI-STATE, and all three are reachable: OMIT for any assignee, the literal `none` for the unassigned bucket, or a user id for that user's items. An empty value is treated as omitted. |
| ancestor string[] · optional | query | SCOPE the read to the ready leaves STRICTLY BENEATH one or more containers, at ANY depth, as `?ancestor=MOTIR-42&ancestor=MOTIR-43` — an any-of set, like `kind`. The named container is NOT in its own result, so a childless one returns an empty page rather than itself: that is the honest answer to “what is ready under this story” for a story nobody has decomposed. ⚠️ It NARROWS the same answer the unfaceted read gives and can never widen it — a leaf whose ancestor chain reaches the named container but is not itself all-ready stays absent, because the parent-ready cascade is computed first and this filters its result (with `allowSoftBlock=true` it narrows that widened answer in the same way). An unknown key, or one belonging to another project, is a 422 — indistinguishable from each other. |
| sprintId string · optional | query | SCOPE the read to the items whose OWN `sprintId` matches — a sprint id, or the reserved literal `active` for the project's active sprint. SINGLE-VALUED: membership is a scalar column, so there is no any-of question to ask. Membership is DIRECT and never inherited — an item under an in-sprint parent but not itself in the sprint is out of scope. A sprint that is not this project's, and `active` on a project between sprints, are both a 422 rather than a silently unfiltered page. An empty value is treated as omitted. |
| allowSoftBlock string · optional | query | WIDEN the read past a SOFT block. `true` keeps the requirement that every listed leaf’s OWN `blocked_by` dependencies are done, but no longer drops a leaf because an ANCESTOR is not ready — so a leaf held only by its epic’s or story’s block is listed, while a leaf with its own open blocker (a HARD block) never is. A container whose own blockers are open counts toward its children only as an ancestor. Composes with every other parameter, `ancestor` included. Absent, empty or `false` returns exactly the parent-ready cascade described above. Any other value is a 422. |
Esempio
curl
curl -X GET 'https://app.motir.co/api/v1/projects/{projectKey}/ready/leaves' \
-H 'Authorization: Bearer $MOTIR_TOKEN'Risposte
| Stato | Condizione |
|---|---|
| 200 | A page of ready leaves, grouped by runnable container, with their edges. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
PageEnvelope & object
List a project’s repositories
The project’s repository SET, in set order — the FIRST row is the project’s primary repository. Each row carries what a client needs to FIND and CLONE one: the checkout `name` (the host’s own casing, which is what a `targetRepo` pin stores), `repoRef`, `cloneUrl`, `defaultBranch` and `archived`. ⚠️ A row with no repository behind it yet is PRESENT with those fields `null` and its own `state` — a `proposed` row is a real member of the set, and a client must be able to say why it was skipped rather than watch it vanish. Branch on `established`, which is the same two-part rule (`state` is `created` or `connected` AND the repository is still connected) that dispatch itself resolves a checkout with. `cloneUrl` is DERIVED, and is `null` for a provider this build cannot address — the API never invents a host. Gated on `project:browse`, the key a CLI-minted token already holds. Requires the `project:browse` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| projectKey string · required | path | The project’s key — the prefix of its work items’ keys, e.g. `MOTIR`. |
| cursor string · optional | query | An opaque page cursor from a previous response’s `nextCursor`. Omit for the first page. Cursors are signed and scoped to their own collection — one issued elsewhere is a 422, never a silent reset. |
| limit integer · optional | query | Rows per page. Defaults to 50; a larger value is CLAMPED to 100 rather than rejected. |
Esempio
curl
curl -X GET 'https://app.motir.co/api/v1/projects/{projectKey}/repositories' \
-H 'Authorization: Bearer $MOTIR_TOKEN'Risposte
| Stato | Condizione |
|---|---|
| 200 | A page of the project’s repositories, primary first. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
PageEnvelope & object
List a project’s sprints
The project’s sprints in sequence order, cursor-paged. Requires the `project:browse` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| projectKey string · required | path | The project’s key — the prefix of its work items’ keys, e.g. `MOTIR`. |
| cursor string · optional | query | An opaque page cursor from a previous response’s `nextCursor`. Omit for the first page. Cursors are signed and scoped to their own collection — one issued elsewhere is a 422, never a silent reset. |
| limit integer · optional | query | Rows per page. Defaults to 50; a larger value is CLAMPED to 100 rather than rejected. |
Esempio
curl
curl -X GET 'https://app.motir.co/api/v1/projects/{projectKey}/sprints' \
-H 'Authorization: Bearer $MOTIR_TOKEN'Risposte
| Stato | Condizione |
|---|---|
| 200 | A page of sprints. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
PageEnvelope & object
Create a planned sprint
Create a sprint in the `planned` state. ⚠️ TWO gates apply: the token must be granted `sprint:manage`, AND its OWNER must be a sprint admin — a grant narrows a role and never widens it, so an ordinary member’s token is refused with the distinct `NOT_SPRINT_ADMIN` code rather than `INSUFFICIENT_PERMISSION`. The `Location` header names the created sprint. Requires the `sprint:manage` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| projectKey string · required | path | The project’s key — the prefix of its work items’ keys, e.g. `MOTIR`. |
Corpo della richiesta
The sprint to create.
| Property | Type | Description |
|---|---|---|
| name optional | string | |
| goal optional | string | null | |
| startDate optional | string | null | |
| endDate optional | string | null |
Esempio
curl
curl -X POST 'https://app.motir.co/api/v1/projects/{projectKey}/sprints' \
-H 'Authorization: Bearer $MOTIR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{}'Risposte
| Stato | Condizione |
|---|---|
| 201 | The created sprint. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| id required | string | |
| name required | string | |
| goal required | string | null | |
| state required | string planned · active · complete | |
| startDate required | string (date-time) | null | |
| endDate required | string (date-time) | null | |
| completedAt required | string (date-time) | null | |
| sequence required | integer | |
| issueCount required | integer | |
| committedPoints required | number | null | |
| committedIssueCount required | integer | null |
List a project’s work items
A cursor-paged collection of a project’s work items, optionally narrowed by a filter expression. Ordered by `(createdAt, id)` ascending — the position the cursor encodes. Requires the `project:browse` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| projectKey string · required | path | The project’s key — the prefix of its work items’ keys, e.g. `MOTIR`. |
| cursor string · optional | query | An opaque page cursor from a previous response’s `nextCursor`. Omit for the first page. A cursor is signed and scoped to its collection — one issued elsewhere is a 422, never a silent reset. |
| limit integer · optional | query | Rows per page. Defaults to 50; a larger value is CLAMPED to 100 rather than rejected. |
| filter string · optional | query | A serialised filter expression, in the same form the product’s own list views use. An unknown field, operator or value is a 422 naming which. |
Esempio
curl
curl -X GET 'https://app.motir.co/api/v1/projects/{projectKey}/work-items' \
-H 'Authorization: Bearer $MOTIR_TOKEN'Risposte
| Stato | Condizione |
|---|---|
| 200 | A page of work-item summaries. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
PageEnvelope & object
Create a work item
Create a work item in a project. The parent, if given, is named by its key and must be a kind-legal parent in the same project. Alternatively send `folderId` to file the new item into one of the project’s folders; naming both a parent and a folder is refused with `PLACEMENT_CONFLICT`. `obsolescence` (`outdated` / `deprecated`) and `obsolescenceNoteMd` may be set on any kind; a value outside the enum is refused with `INVALID_OBSOLESCENCE`. A MARK is a finished card’s state: a new item lands at the workflow’s initial status, so a mark on create is refused with `OBSOLESCENCE_REQUIRES_FINISHED` (422, `item`: the key and status) unless that status is in the done category — set it once the item is finished, and archive an item nobody will finish instead. A parent that carries a mark takes no new children: `MARKED_CARD_CANNOT_REOPEN` (422, `mark`: the parent’s key and mark). Requires the `work_item:edit` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| projectKey string · required | path | The project’s key — the prefix of its work items’ keys, e.g. `MOTIR`. |
Corpo della richiesta
The work item to create.
| Property | Type | Description |
|---|---|---|
| kind required | string epic · story · task · subtask · bug | |
| title required | string | |
| parentKey optional | string | null | |
| folderId optional | string | null | |
| descriptionMd optional | string | null | |
| priority optional | string lowest · low · medium · high · highest | |
| type optional | string | null code · design · test · content · copy · translate · research · review · verification · decision · choice · deploy · manual · legal · chore | |
| executor optional | string | null coding_agent · human | |
| difficulty optional | string | null trivial · low · medium · high | |
| obsolescence optional | string | null outdated · deprecated | |
| obsolescenceNoteMd optional | string | null | |
| storyPoints optional | number | null | |
| estimateMinutes optional | integer | null | |
| targetRepo optional | string | null | |
| targetRepos optional | string[] | |
| targetRepositories optional | string[] | |
| assigneeId optional | string | null | |
| dueDate optional | string (date-time) | null |
Esempio
curl
curl -X POST 'https://app.motir.co/api/v1/projects/{projectKey}/work-items' \
-H 'Authorization: Bearer $MOTIR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"kind":"epic","title":"<title>"}'Risposte
| Stato | Condizione |
|---|---|
| 201 | The created work item. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| key required | string | |
| kind required | string epic · story · task · subtask · bug | |
| type required | string | null code · design · test · content · copy · translate · research · review · verification · decision · choice · deploy · manual · legal · chore | |
| title required | string | |
| status required | string | |
| priority required | string lowest · low · medium · high · highest | |
| assigneeId required | string | null | |
| reporterId required | string | |
| dueDate required | string (date-time) | null | |
| estimateMinutes required | integer | null | |
| storyPoints required | number | null | |
| createdAt required | string (date-time) | |
| updatedAt required | string (date-time) | |
| obsolescence required | string | null outdated · deprecated | |
| obsolescenceNoteMd required | string | null | |
| descriptionMd required | string | null | |
| parentKey required | string | null | |
| folderId required | string | null | |
| folderPath required | string[] | null | |
| ancestorKeys required | string[] | |
| children required | object[] | |
| links required | object | |
| readiness required | object | |
| labels required | object[] | |
| components required | object[] | |
| commentCount required | integer | |
| sprintId required | string | null | |
| targetRepo required | string | null | |
| targetRepos required | string[] | |
| targetRepositories required | object[] | |
| executor required | string | null coding_agent · human | |
| difficulty required | string | null trivial · low · medium · high | |
| planningSource required | string | null native · mcp · manual · api | |
| planningHarness required | string | null | |
| planningModel required | string | null | |
| implementationSource required | string | null hosted · byok · manual | |
| implementationHarness required | string | null | |
| implementationModel required | string | null | |
| archivedAt required | string (date-time) | null | |
| deliveries required | object[] |
Count a project’s work items
How many work items match a filter, in ONE request and without paging the match set. Takes the same `filter` the collection takes, and counts exactly what that collection would page. Exact, never capped. Requires the `project:browse` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| projectKey string · required | path | The project’s key — the prefix of its work items’ keys, e.g. `MOTIR`. |
| filter string · optional | query | A serialised filter expression, in the same form the collection takes. Omit to count every work item in the project. An unknown field, operator or value is a 422 naming which. |
Esempio
curl
curl -X GET 'https://app.motir.co/api/v1/projects/{projectKey}/work-items/count' \
-H 'Authorization: Bearer $MOTIR_TOKEN'Risposte
| Stato | Condizione |
|---|---|
| 200 | How many work items match. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| count required | integer |
Atomically claim a whole story or sprint, all or nothing
CLAIM an entire SCOPE — a container work item and its children, or a project’s ACTIVE sprint — so a scoped run owns the whole set before its first agent starts. In ONE transaction the scope is validated, every row is locked in a deterministic order, every row’s status is re-checked against the TO-DO category, and — if all of them hold — every row is assigned to the caller AND moved to “In progress”. ⚠️ ALL OR NOTHING: if ANY member is un-claimable the whole claim rolls back and NOTHING is written, because a partially-claimed scope is the one outcome with no good handling — you can neither finish it nor cleanly abandon it. ⚠️ EVERY WORK ITEM IN A CLAIMED SCOPE READS “In progress” FOR THE WHOLE RUN, while only one of them is being worked at a time. That is deliberate and it changes what the status MEANS: from “an agent is on this right now” to “this run owns it”. The board therefore shows the run’s FOOTPRINT rather than its cursor — the price of exclusive ownership, which is what lets a scoped run promise to finish what it started. A refusal is a 200 with an `outcome`: `claimed`, `mine` (already yours — resume), `taken` (a member is held by somebody else, and they are named), `not_claimable` (a member is finished or under review), `wrong_shape` (a work-item scope whose child is itself a container — re-plan it, do not retry), `not_finishable` (work OUTSIDE the scope gates work inside it). A STORY scope is ONE LAYER and that is checked; a SPRINT scope may span many layers and no shape check applies, because `validate_sprint` has already guaranteed its membership is closed. A sprint’s scope is exactly the items whose OWN `sprintId` matches — an item under an in-sprint parent but not itself in the sprint is NOT claimed. The claim IS the dispatch status flip — do not also POST a transition afterwards. Requires the `work_item:edit` permission.
Corpo della richiesta
The scope to claim: `{ "kind": "work_item", "key": "MOTIR-42" }` for a container and its children, or `{ "kind": "sprint", "projectKey": "MOTIR" }` for that project’s active sprint.
| Property | Type | Description |
|---|---|---|
| kind required | string | |
| key required | string | |
| exceptLanded optional | boolean |
Esempio
curl
curl -X POST 'https://app.motir.co/api/v1/scope-claims' \
-H 'Authorization: Bearer $MOTIR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"kind":"<kind>","key":"<key>"}'Risposte
| Stato | Condizione |
|---|---|
| 200 | What the claim resolved to, and — on a refusal — why. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 409 | The request conflicts with existing state. The body is well-formed; the state is not what the request assumed. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| scope required | object | |
| outcome required | string claimed · mine · taken · not_claimable · wrong_shape · not_finishable | |
| claimed required | boolean | |
| members required | object[] | |
| offender required | object | null | |
| shape required | object | null | |
| blockers required | object[] |
Close out a merged session branch
Close out a session branch after its pull request is merged: every work item recorded on the branch moves to “Done” and its recorded branch is cleared. Returns a PER-ITEM outcome (`completed` / `already_done` / `failed`) — a partial close-out is a real result, not an error: the items that could close DID, and the ones that could not are named with a reason. Read the results; do not infer an outcome from their count. A branch nothing is recorded on returns an empty list, not a 404. The branch travels in the BODY because a git ref routinely contains `/`. Requires the `work_item:edit` permission.
Corpo della richiesta
The merged session branch, and optional provenance for every item closed.
| Property | Type | Description |
|---|---|---|
| sessionBranch required | string | |
| implementationSource optional | string byok · manual | |
| implementationHarness optional | string | |
| implementationModel optional | string |
Esempio
curl
curl -X POST 'https://app.motir.co/api/v1/sessions/complete' \
-H 'Authorization: Bearer $MOTIR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"sessionBranch":"<sessionBranch>"}'Risposte
| Stato | Condizione |
|---|---|
| 200 | The branch and one outcome per item that was recorded on it. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| sessionBranch required | string | |
| results required | object[] |
Read a sprint
One sprint by id. A sprint in another workspace and one that never existed are the same 404 — the existence-oracle rule. Requires the `project:browse` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| sprintId string · required | path | The sprint’s id. A sprint has no `MOTIR-<n>` key, so its id is its name on the wire. |
Esempio
curl
curl -X GET 'https://app.motir.co/api/v1/sprints/{sprintId}' \
-H 'Authorization: Bearer $MOTIR_TOKEN'Risposte
| Stato | Condizione |
|---|---|
| 200 | The sprint. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| id required | string | |
| name required | string | |
| goal required | string | null | |
| state required | string planned · active · complete | |
| startDate required | string (date-time) | null | |
| endDate required | string (date-time) | null | |
| completedAt required | string (date-time) | null | |
| sequence required | integer | |
| issueCount required | integer | |
| committedPoints required | number | null | |
| committedIssueCount required | integer | null |
Update a sprint
Patch a sprint’s name, goal or window. A COMPLETED sprint is frozen: the body is fine, the state is not, so the refusal is a 409 rather than a 422. Requires the `sprint:manage` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| sprintId string · required | path | The sprint’s id. A sprint has no `MOTIR-<n>` key, so its id is its name on the wire. |
Corpo della richiesta
The fields to change.
| Property | Type | Description |
|---|---|---|
| name optional | string | |
| goal optional | string | null | |
| startDate optional | string | null | |
| endDate optional | string | null |
Esempio
curl
curl -X PATCH 'https://app.motir.co/api/v1/sprints/{sprintId}' \
-H 'Authorization: Bearer $MOTIR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{}'Risposte
| Stato | Condizione |
|---|---|
| 200 | The updated sprint. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 409 | The request conflicts with existing state. The body is well-formed; the state is not what the request assumed. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| id required | string | |
| name required | string | |
| goal required | string | null | |
| state required | string planned · active · complete | |
| startDate required | string (date-time) | null | |
| endDate required | string (date-time) | null | |
| completedAt required | string (date-time) | null | |
| sequence required | integer | |
| issueCount required | integer | |
| committedPoints required | number | null | |
| committedIssueCount required | integer | null |
Complete a sprint
Close an active sprint, optionally carrying its unfinished items over to a named target. Completing a sprint that is not active is a 422. Requires the `sprint:manage` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| sprintId string · required | path | The sprint’s id. A sprint has no `MOTIR-<n>` key, so its id is its name on the wire. |
Corpo della richiesta
Where unfinished items go, if anywhere.
| Property | Type | Description |
|---|---|---|
| carryOverTo optional | string | object |
Esempio
curl
curl -X POST 'https://app.motir.co/api/v1/sprints/{sprintId}/complete' \
-H 'Authorization: Bearer $MOTIR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{}'Risposte
| Stato | Condizione |
|---|---|
| 200 | The completed sprint. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| id required | string | |
| name required | string | |
| goal required | string | null | |
| state required | string planned · active · complete | |
| startDate required | string (date-time) | null | |
| endDate required | string (date-time) | null | |
| completedAt required | string (date-time) | null | |
| sequence required | integer | |
| issueCount required | integer | |
| committedPoints required | number | null | |
| committedIssueCount required | integer | null |
Start a sprint
Move a planned sprint to active. ⚠️ Losing the race to activate is a 409, not a 422: the request was valid when it was sent and another one committed first, so the right instruction is re-read-and-retry rather than fix-your-body. Starting a sprint that is not planned is a 422 — a state the caller can see from a read. Requires the `sprint:manage` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| sprintId string · required | path | The sprint’s id. A sprint has no `MOTIR-<n>` key, so its id is its name on the wire. |
Corpo della richiesta
The sprint window, if it is being set here.
| Property | Type | Description |
|---|---|---|
| name optional | string | |
| goal optional | string | null | |
| startDate optional | string | null | |
| endDate optional | string | null |
Esempio
curl
curl -X POST 'https://app.motir.co/api/v1/sprints/{sprintId}/start' \
-H 'Authorization: Bearer $MOTIR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{}'Risposte
| Stato | Condizione |
|---|---|
| 200 | The active sprint. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 409 | The request conflicts with existing state. The body is well-formed; the state is not what the request assumed. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| id required | string | |
| name required | string | |
| goal required | string | null | |
| state required | string planned · active · complete | |
| startDate required | string (date-time) | null | |
| endDate required | string (date-time) | null | |
| completedAt required | string (date-time) | null | |
| sequence required | integer | |
| issueCount required | integer | |
| committedPoints required | number | null | |
| committedIssueCount required | integer | null |
List a sprint’s members
The items in a sprint, in rank order. ⚠️ Deliberately asymmetric with the backlog: done items STAY in their sprint, because that is what makes a completed sprint a historical record. Reports a total, because the read behind it already computes one. Requires the `project:browse` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| sprintId string · required | path | The sprint’s id. A sprint has no `MOTIR-<n>` key, so its id is its name on the wire. |
| cursor string · optional | query | An opaque page cursor from a previous response’s `nextCursor`. Omit for the first page. Cursors are signed and scoped to their own collection — one issued elsewhere is a 422, never a silent reset. |
| limit integer · optional | query | Rows per page. Defaults to 50; a larger value is CLAMPED to 100 rather than rejected. |
| filter string · optional | query | A serialised filter expression, in the same grammar the product’s own list views use — never an ad-hoc `?status=&assignee=` axis. An unknown field, operator or value is a 422 naming which. |
Esempio
curl
curl -X GET 'https://app.motir.co/api/v1/sprints/{sprintId}/work-items' \
-H 'Authorization: Bearer $MOTIR_TOKEN'Risposte
| Stato | Condizione |
|---|---|
| 200 | A page of sprint members, with the total behind it. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
RankedPageEnvelope & object
Move work items into a sprint
An atomic batch move into this sprint. An empty array is a 200 no-op; an item belonging to another project rejects the WHOLE batch before any write, so a partial move cannot happen. Requires the `sprint:manage` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| sprintId string · required | path | The sprint’s id. A sprint has no `MOTIR-<n>` key, so its id is its name on the wire. |
Corpo della richiesta
The work items to move.
| Property | Type | Description |
|---|---|---|
| workItemKeys required | string[] |
Esempio
curl
curl -X POST 'https://app.motir.co/api/v1/sprints/{sprintId}/work-items' \
-H 'Authorization: Bearer $MOTIR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"workItemKeys":[]}'Risposte
| Stato | Condizione |
|---|---|
| 200 | The keys that moved, in request order. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| movedKeys required | string[] |
Read a work item
The full work item: its own fields, its parent and children, its five link groups, its readiness verdict, its comment count and — when it is filed — its folder (`folderId` and the root-first `folderPath`). The response carries an `ETag` for use as an `If-Match` on a later update. Requires the `project:browse` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| key string · required | path | The work item’s `MOTIR-<n>` key. Never its internal id (ADR §7). |
Esempio
curl
curl -X GET 'https://app.motir.co/api/v1/work-items/{key}' \
-H 'Authorization: Bearer $MOTIR_TOKEN'Risposte
| Stato | Condizione |
|---|---|
| 200 | The work item. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| key required | string | |
| kind required | string epic · story · task · subtask · bug | |
| type required | string | null code · design · test · content · copy · translate · research · review · verification · decision · choice · deploy · manual · legal · chore | |
| title required | string | |
| status required | string | |
| priority required | string lowest · low · medium · high · highest | |
| assigneeId required | string | null | |
| reporterId required | string | |
| dueDate required | string (date-time) | null | |
| estimateMinutes required | integer | null | |
| storyPoints required | number | null | |
| createdAt required | string (date-time) | |
| updatedAt required | string (date-time) | |
| obsolescence required | string | null outdated · deprecated | |
| obsolescenceNoteMd required | string | null | |
| descriptionMd required | string | null | |
| parentKey required | string | null | |
| folderId required | string | null | |
| folderPath required | string[] | null | |
| ancestorKeys required | string[] | |
| children required | object[] | |
| links required | object | |
| readiness required | object | |
| labels required | object[] | |
| components required | object[] | |
| commentCount required | integer | |
| sprintId required | string | null | |
| targetRepo required | string | null | |
| targetRepos required | string[] | |
| targetRepositories required | object[] | |
| executor required | string | null coding_agent · human | |
| difficulty required | string | null trivial · low · medium · high | |
| planningSource required | string | null native · mcp · manual · api | |
| planningHarness required | string | null | |
| planningModel required | string | null | |
| implementationSource required | string | null hosted · byok · manual | |
| implementationHarness required | string | null | |
| implementationModel required | string | null | |
| archivedAt required | string (date-time) | null | |
| deliveries required | object[] |
Update a work item
Patch any subset of a work item’s fields. A field that is ABSENT is untouched; a field explicitly set to `null` CLEARS it. Send `If-Match` to make the update conditional on the item not having moved. `folderId` files the item into a folder (or `null` takes it out), in the same write as every other field; setting `parentKey` on a filed item takes it out of its folder, and sending both is refused with `PLACEMENT_CONFLICT`. `obsolescence` and `obsolescenceNoteMd` mark the item as no longer true of the code on ANY kind, and `null` clears either; a value outside the enum is refused with `INVALID_OBSOLESCENCE`. A MARK is a FINISHED card’s state: setting `outdated` or `deprecated` on an item whose status is outside the done category is refused with `OBSOLESCENCE_REQUIRES_FINISHED` (422, `item`: the key, the status and its category) — archive an item nobody will finish instead. Clearing is always legal, and a patch that omits the mark never meets it. Re-parenting under a marked item (`parentKey`) is refused with `MARKED_CARD_CANNOT_REOPEN` (422, `mark`). Requires the `work_item:edit` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| key string · required | path | The work item’s `MOTIR-<n>` key. Never its internal id (ADR §7). |
| If-Match string · optional | header | An `ETag` from a previous read of this work item. When present, the update is refused with 412 if the item moved since that read. Omitting it means last-write-wins. |
Corpo della richiesta
The fields to change.
| Property | Type | Description |
|---|---|---|
| kind optional | string epic · story · task · subtask · bug | |
| title optional | string | |
| descriptionMd optional | string | null | |
| explanationMd optional | string | null | |
| parentKey optional | string | null | |
| folderId optional | string | null | |
| priority optional | string lowest · low · medium · high · highest | |
| type optional | string | null code · design · test · content · copy · translate · research · review · verification · decision · choice · deploy · manual · legal · chore | |
| executor optional | string | null coding_agent · human | |
| difficulty optional | string | null trivial · low · medium · high | |
| obsolescence optional | string | null outdated · deprecated | |
| obsolescenceNoteMd optional | string | null | |
| storyPoints optional | number | null | |
| estimateMinutes optional | integer | null | |
| targetRepo optional | string | null | |
| targetRepos optional | string[] | |
| targetRepositories optional | string[] | |
| assigneeId optional | string | null | |
| dueDate optional | string (date-time) | null |
Esempio
curl
curl -X PATCH 'https://app.motir.co/api/v1/work-items/{key}' \
-H 'Authorization: Bearer $MOTIR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{}'Risposte
| Stato | Condizione |
|---|---|
| 200 | The updated work item. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 412 | An `If-Match` precondition failed — the resource moved since the validator was issued. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| key required | string | |
| kind required | string epic · story · task · subtask · bug | |
| type required | string | null code · design · test · content · copy · translate · research · review · verification · decision · choice · deploy · manual · legal · chore | |
| title required | string | |
| status required | string | |
| priority required | string lowest · low · medium · high · highest | |
| assigneeId required | string | null | |
| reporterId required | string | |
| dueDate required | string (date-time) | null | |
| estimateMinutes required | integer | null | |
| storyPoints required | number | null | |
| createdAt required | string (date-time) | |
| updatedAt required | string (date-time) | |
| obsolescence required | string | null outdated · deprecated | |
| obsolescenceNoteMd required | string | null | |
| descriptionMd required | string | null | |
| parentKey required | string | null | |
| folderId required | string | null | |
| folderPath required | string[] | null | |
| ancestorKeys required | string[] | |
| children required | object[] | |
| links required | object | |
| readiness required | object | |
| labels required | object[] | |
| components required | object[] | |
| commentCount required | integer | |
| sprintId required | string | null | |
| targetRepo required | string | null | |
| targetRepos required | string[] | |
| targetRepositories required | object[] | |
| executor required | string | null coding_agent · human | |
| difficulty required | string | null trivial · low · medium · high | |
| planningSource required | string | null native · mcp · manual · api | |
| planningHarness required | string | null | |
| planningModel required | string | null | |
| implementationSource required | string | null hosted · byok · manual | |
| implementationHarness required | string | null | |
| implementationModel required | string | null | |
| archivedAt required | string (date-time) | null | |
| deliveries required | object[] |
Read a work item’s activity — changes, comments, or both
Read a work item’s activity in one of three views: `all` (default — comments and the change trail interleaved in timestamp order), `comments` (the discussion), or `history` (the change trail only). Every entry carries a `type` so one renderer serves all three. The `cursor` is OPAQUE and SCOPED TO ITS VIEW: echo it back verbatim, never construct or parse one, and never hand a cursor from one view to another — that is a 422, not a silent restart. A page may be SHORTER than you expect while more remains (the change scan is noise-filtered and a comment page drags whole reply threads along), so walk until `nextCursor` is `null`, never until a page looks short. `GET /api/v1/work-items/{key}/comments` still exists and is unchanged — this view is the same data through the same read, offered so one code path can walk all three. Requires the `project:browse` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| key string · required | path | The work item’s `MOTIR-<n>` key (case-insensitive). |
| view string · optional | query | Which stream to read. Defaults to `all`. |
| order string · optional | query | Page-walk direction. Omit for each view’s shipped default — `desc` (newest first) for `all` and `history`, `asc` for `comments`. |
| cursor string · optional | query | An opaque page cursor from a previous response’s `nextCursor`. Omit for the first page. Scoped to its own VIEW — one issued elsewhere is a 422, never a silent reset. |
Esempio
curl
curl -X GET 'https://app.motir.co/api/v1/work-items/{key}/activity' \
-H 'Authorization: Bearer $MOTIR_TOKEN'Risposte
| Stato | Condizione |
|---|---|
| 200 | One page of activity entries. `totalCount` is the number of entries in this view; `totalComments` / `totalChanges` break that down for the merged `all` view, and each is null on a view that did not count that source. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
RankedPageEnvelope & object & object
Submit a hosted review run’s ONE verdict
Decide a work item’s `agent_review` gate as the REVIEW AGENT: `pass` approves it, which raises the approve-and-merge gate for the same version; `changes_requested` records `findingsMd` (required, non-empty) as the gate’s note and moves nothing — the card is To fix. `subjectVersion` must be the version the review prompt named. ONE verdict per run (`REVIEW_VERDICT_ALREADY_SUBMITTED`, 409). A verdict for a version the gate no longer asks about, or a gate already superseded or decided, is RECORDED on the run and decides nothing (`REVIEW_STALE`, 409). Accepts ONLY the credential of a `review` dispatch run, for that run’s own card. Requires the `work_item:edit` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| key string · required | path | The work item key, e.g. `ACME-7`. |
Corpo della richiesta
The reviewed version, the verdict, a short summary and the findings.
| Property | Type | Description |
|---|---|---|
| subjectVersion required | string | |
| verdict required | string pass · changes_requested | |
| summaryMd optional | string | null | |
| findingsMd optional | string | null |
Esempio
curl
curl -X POST 'https://app.motir.co/api/v1/work-items/{key}/agent-review' \
-H 'Authorization: Bearer $MOTIR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"subjectVersion":"<subjectVersion>","verdict":"pass"}'Risposte
| Stato | Condizione |
|---|---|
| 200 | The gate the verdict decided and its new state. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 409 | The request conflicts with existing state. The body is well-formed; the state is not what the request assumed. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| key required | string | |
| gateId required | string | |
| verdict required | string pass · changes_requested | |
| state required | string approved · changes_requested | |
| subjectVersion required | string | |
| decidedAt required | string (date-time) |
One gate’s decision record on a work item
The DECISION a person made on one approval gate — its `state`, the `noteMd` they wrote, who wrote it, when, on which `subjectVersion`, under which authority and through which surface (`docs/decisions/approval-gates.md` §6a’s audit set). This is how an agent reads the answer to a question it raised: **Request changes** records its reason in `noteMd`, and before this read every door onto that column was session-authed, so the one gate kind whose author is always an agent — `decision_approval`, raised only on a `type: decision` + `executor: coding_agent` card — was the one kind whose refusal an agent could not read. ⚠️ `gate: null` is an ANSWER, not a miss: the card has no gate of that kind, so nothing is waiting and nothing was decided; a key that does not resolve is the 404 instead. Read `state` BEFORE the audit fields — five of them are written by the decision, so a null means *not yet decided* and never *decided by nobody*. ⚠️ A READ ONLY: nothing on this API decides a gate, and nothing here asserts `approval:decide_any` — deciding stays session-authed, deliberately (§1, §2). Requires the `project:browse` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| key string · required | path | The work item’s `MOTIR-<n>` key. Never its internal id (ADR §7). |
| kind string · required | query | Which gate to read. A card carries at most one LIVE gate per kind, and this answers the one the approval frame shows: a live question wins over a decided one, and among decided ones the newest. |
Esempio
curl
curl -X GET 'https://app.motir.co/api/v1/work-items/{key}/approval-gate' \
-H 'Authorization: Bearer $MOTIR_TOKEN'Risposte
| Stato | Condizione |
|---|---|
| 200 | The gate’s decision record, or `gate: null` when the card has none of that kind. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| workItemKey required | string | |
| workItemTitle required | string | |
| kind required | string design_result · decision_approval · pull_request_approval · pull_request_merge · acceptance_result · decision_choice · decision_confirmation · plan_approval · agent_review · manual_work | |
| gate required | object | null | |
| routedToLabel required | string | null |
Archive a work item
A recoverable soft-remove. Does NOT cascade to children — the irreversible subtree delete is not exposed by this API at all (ADR §3). Requires the `work_item:archive` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| key string · required | path | The work item’s `MOTIR-<n>` key. Never its internal id (ADR §7). |
Esempio
curl
curl -X POST 'https://app.motir.co/api/v1/work-items/{key}/archive' \
-H 'Authorization: Bearer $MOTIR_TOKEN'Risposte
| Stato | Condizione |
|---|---|
| 200 | The archived work item. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| key required | string | |
| kind required | string epic · story · task · subtask · bug | |
| type required | string | null code · design · test · content · copy · translate · research · review · verification · decision · choice · deploy · manual · legal · chore | |
| title required | string | |
| status required | string | |
| priority required | string lowest · low · medium · high · highest | |
| assigneeId required | string | null | |
| reporterId required | string | |
| dueDate required | string (date-time) | null | |
| estimateMinutes required | integer | null | |
| storyPoints required | number | null | |
| createdAt required | string (date-time) | |
| updatedAt required | string (date-time) | |
| obsolescence required | string | null outdated · deprecated | |
| obsolescenceNoteMd required | string | null | |
| descriptionMd required | string | null | |
| parentKey required | string | null | |
| folderId required | string | null | |
| folderPath required | string[] | null | |
| ancestorKeys required | string[] | |
| children required | object[] | |
| links required | object | |
| readiness required | object | |
| labels required | object[] | |
| components required | object[] | |
| commentCount required | integer | |
| sprintId required | string | null | |
| targetRepo required | string | null | |
| targetRepos required | string[] | |
| targetRepositories required | object[] | |
| executor required | string | null coding_agent · human | |
| difficulty required | string | null trivial · low · medium · high | |
| planningSource required | string | null native · mcp · manual · api | |
| planningHarness required | string | null | |
| planningModel required | string | null | |
| implementationSource required | string | null hosted · byok · manual | |
| implementationHarness required | string | null | |
| implementationModel required | string | null | |
| archivedAt required | string (date-time) | null | |
| deliveries required | object[] |
Attach a file to a work item
Upload a file and attach it to the work item, as `multipart/form-data` with a single `file` part. This is the GENERAL door: it carries no artifact kind and belongs to no lifecycle, so any deliverable a work item produces — a research findings document, a review’s notes — can reach the item that commissioned it. A DESIGN asset does not use this endpoint; it has its own publisher, whose result renders in the Design result panel. ⚠️ Two size limits apply and the SMALLER one is not this API’s: the organization’s plan sets a per-file entitlement (10 MB, or 100 MB on a paid plan) and is what returns 413, while a direct upload is separately capped at roughly 4.5 MB by the serving platform and is refused before the request reaches Motir. Requires the `work_item:edit` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| key string · required | path | The work item’s `MOTIR-<n>` key. Never its internal id (ADR §7). |
Corpo della richiesta
The file to attach, in a `file` part. An empty part is a 422.
| Property | Type | Description |
|---|---|---|
| file required | string |
Esempio
curl
curl -X POST 'https://app.motir.co/api/v1/work-items/{key}/attachments' \
-H 'Authorization: Bearer $MOTIR_TOKEN' \
-H 'Content-Type: multipart/form-data' \
-d '{"file":"<file>"}'Risposte
| Stato | Condizione |
|---|---|
| 201 | The created attachment. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 402 | A plan entitlement is exhausted — the workspace owner’s AI credits, or the organization’s total attachment-storage cap. The request was valid; it was refused for want of headroom, and retrying will not help until the limit is lifted. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 413 | The uploaded file is larger than the per-file limit this organization’s plan allows. Note the SEPARATE platform ceiling on a direct upload, documented on the operation itself (docs/decisions/attachment-api-door.md §1). |
| 415 | The uploaded file’s media type is not on the allowlist. `text/html` is deliberately absent: the three layers that make HTML safe to serve belong to the design-result lifecycle and its own publisher (design-result.md §5a). |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| id required | string | |
| workItemKey required | string | |
| filename required | string | |
| mimeType required | string | |
| sizeBytes required | integer | |
| source required | string editor · panel · api | |
| contentPath required | string | |
| uploader required | object | |
| createdAt required | string |
Atomically claim one work item by key
CLAIM one work item, named by key, so that concurrent dispatchers cannot both start it. In ONE transaction the row is locked, its status is re-checked against the TO-DO category, and — if it holds — the item is assigned to the caller AND moved to “In progress”. The to-do category is `todo` AND `blocked`, so a deliberately forced dispatch of an item whose dependencies are unmet still works. ⚠️ A refusal is a 200 with an `outcome`, not an error, because three of the four outcomes are ordinary: `claimed` (it is yours), `mine` (already yours — resume your own interrupted run), `taken` (somebody else holds it, and they are named), `not_claimable` (finished, under review, or otherwise outside the to-do category). Claiming is IDEMPOTENT for the holder and never re-opens finished work. The claim IS the dispatch status flip — do not also POST a transition afterwards. Requires the `work_item:edit` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| key string · required | path | The work item’s `MOTIR-<n>` key (case-insensitive). |
Esempio
curl
curl -X POST 'https://app.motir.co/api/v1/work-items/{key}/claim' \
-H 'Authorization: Bearer $MOTIR_TOKEN'Risposte
| Stato | Condizione |
|---|---|
| 200 | What the claim resolved to, and who holds the item. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| key required | string | |
| title required | string | |
| outcome required | string claimed · mine · taken · not_claimable | |
| claimed required | boolean | |
| status required | object | |
| assignee required | object | null | |
| transitionedBy required | object | null | |
| transitionedAt required | string (date-time) | null |
List a work item’s comments
Root comments with their single-level reply threads, cursor-paged. This collection DOES report a total, because the shipped read computes it as a bounded aggregate. Requires the `project:browse` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| key string · required | path | The work item’s `MOTIR-<n>` key. Never its internal id (ADR §7). |
| cursor string · optional | query | An opaque page cursor from a previous response’s `nextCursor`. Omit for the first page. A cursor is signed and scoped to its collection — one issued elsewhere is a 422, never a silent reset. |
| limit integer · optional | query | Rows per page. Defaults to 50; a larger value is CLAMPED to 100 rather than rejected. |
| order string · optional | query | Root-comment order — `asc` (oldest first, the default) or `desc`. |
Esempio
curl
curl -X GET 'https://app.motir.co/api/v1/work-items/{key}/comments' \
-H 'Authorization: Bearer $MOTIR_TOKEN'Risposte
| Stato | Condizione |
|---|---|
| 200 | A page of comment threads, with the total behind it. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
RankedPageEnvelope & object
Comment on a work item
Add a root comment, or a reply by naming a root comment as its parent. Replies are single-level: a reply to a reply is a 422. Requires the `comment:add` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| key string · required | path | The work item’s `MOTIR-<n>` key. Never its internal id (ADR §7). |
Corpo della richiesta
The comment to add.
| Property | Type | Description |
|---|---|---|
| bodyMd required | string | |
| parentCommentId optional | string |
Esempio
curl
curl -X POST 'https://app.motir.co/api/v1/work-items/{key}/comments' \
-H 'Authorization: Bearer $MOTIR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"bodyMd":"<bodyMd>"}'Risposte
| Stato | Condizione |
|---|---|
| 201 | The created comment. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| id required | string | |
| parentCommentId required | string | null | |
| authorId required | string | |
| author required | object | |
| bodyMd required | string | |
| createdAt required | string (date-time) | |
| editedAt required | string (date-time) | null | |
| mentionedUserIds required | string[] |
Claim the continue of a work item whose last run died
Take over a work item whose last run DIED (`motir continue <key>`), for any member who may edit the project, and hand back what the continuing agent needs: the dead run, the branch its work is on, and its open pull request. In ONE transaction the item’s row is locked and, in order: an open dispatch run with command `continue` already holding the item answers `mine` (yours — same `runId`, the branch again) or `taken` (named, with its start); a run that is still ALIVE (a local run that heartbeat within 5 minutes, an open hosted run) is `not_continuable` (`run_alive`, naming its dispatcher); an item at Implemented / In Review / Approved is `use_fix` (its pull request is open — CI or `motir fix` owns it); any other status but In Progress is `not_in_progress`; a leg of a dead PARENT run is `continue_the_parent` (naming `parentKey`); no run that ended without success is `no_dead_run`; a run that STOPPED AT A GATE (closed `gated`) whose gates all still wait is `gate_awaiting`, and one whose gates were sent back and none approved is `gate_sent_back` (both naming `gates`); a dead run that left no branch is `no_branch`. A gated run with an APPROVED gate is taken over like a dead one, as a resume (`resumesGated`, the approved `gates`). Otherwise a LAPSED run still reading `running` is closed `abandoned`, the item is RE-ASSIGNED to the caller, and a `continue` run is opened: `claimed`. ⚠️ A refusal is a 200 with an `outcome`, not an error. ⚠️ The item’s STATUS is never written. Heartbeat the run (`heartbeatDispatchRun`) and CLOSE it (`closeDispatchRun`) when the continue ends. Requires the `work_item:edit` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| key string · required | path | The work item’s `MOTIR-<n>` key (case-insensitive). |
Esempio
curl
curl -X POST 'https://app.motir.co/api/v1/work-items/{key}/continue' \
-H 'Authorization: Bearer $MOTIR_TOKEN'Risposte
| Stato | Condizione |
|---|---|
| 200 | What the continue claim resolved to, the `continue` run, the dead run and the branch. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| key required | string | |
| title required | string | |
| outcome required | string claimed · mine · taken · not_continuable | |
| reason required | string | null run_alive · use_fix · not_in_progress · continue_the_parent · no_dead_run · no_branch · gate_awaiting · gate_sent_back | |
| parentKey required | string | null | |
| runId required | string | null | |
| holder required | object | null | |
| startedAt required | string (date-time) | null | |
| deadRun required | object | null | |
| branch required | string | null | |
| branches required | object[] | |
| pullRequest required | object | null | |
| previousAssignee required | object | null | |
| mode required | string card · parent | |
| landedKeys required | string[] | |
| resumedKeys required | string[] | |
| gates required | object[] | |
| resumesGated required | boolean |
One design card’s approved design
The verdict for ONE design card, addressed by its own key — the read behind following a design a `…/designs` verdict named, and behind finding a delta mock’s amended base by its `sourcePath`. Same verdict rules and same link terms as `…/designs`. Requires the `project:browse` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| key string · required | path | The work item’s `MOTIR-<n>` key. Never its internal id (ADR §7). |
Esempio
curl
curl -X GET 'https://app.motir.co/api/v1/work-items/{key}/design' \
-H 'Authorization: Bearer $MOTIR_TOKEN'Risposte
| Stato | Condizione |
|---|---|
| 200 | The design card’s verdict. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| verdict required | string approved · not_approved | |
| designCardKey required | string | |
| designCardTitle required | string | |
| design optional | object | |
| reason optional | string not_a_design_card · not_done · cancelled · withdrawn · no_result |
The approved designs a work item waits on
One verdict per work item this one is `blocked_by`, in key order — the design an agent is handed before it builds (`docs/decisions/design-result.md` AMENDMENT 5 Q4). A verdict is `approved` with the version an APPROVAL named, or `not_approved` with one of five reasons: the blocker is not a design card, the design card is not `done`, it was cancelled, its result was withdrawn, or it never published one. ⚠️ The approved design is NOT simply the card’s current result: a design card approved and then republished before its merge would otherwise hand back a version nobody approved (AMENDMENT 5 Q2). Every `available` asset of an `approved` verdict carries a short-lived `url` and its `expiresAt`; an `unavailable` asset — an approved version whose bytes the orphan-GC reclaimed — carries neither, and that is a real answer rather than a failure. Requires the `project:browse` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| key string · required | path | The work item’s `MOTIR-<n>` key. Never its internal id (ADR §7). |
Esempio
curl
curl -X GET 'https://app.motir.co/api/v1/work-items/{key}/designs' \
-H 'Authorization: Bearer $MOTIR_TOKEN'Risposte
| Stato | Condizione |
|---|---|
| 200 | The design verdicts for every design card this item waits on. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| designs required | object[] |
Read the canonical coding-agent prompt for a work item
Return the server-assembled prompt for one work item — the CONTEXT / WHAT TO DO / ACCEPTANCE CRITERIA / GIT WORKFLOW sections built from the item, its parent, its dependencies and its repo — plus the repo to run it in and which git workflow it carries. A PURE READ: it does not claim the item, move its status, or change its recorded session branch, so fetching a prompt to look at it is always safe. The text is deliberately identical for every agent harness; do not rewrite it. `advisories` is never a gate — it changes what you are told, never what you may do. Requires the `project:browse` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| key string · required | path | The work item’s `MOTIR-<n>` key (case-insensitive). |
| sessionBranch string · optional | query | A session branch to FALL BACK to when this item carries no lineage of its own — the unattended-run seed. It never overrides: an item whose dependencies are already integrated, or that is itself integrated, keeps its own branch, so a caller cannot redirect a live lineage. |
| autoApproveReplan string · optional | query | `1` when THIS run’s loop is willing to approve a submitted re-plan itself and carry on (`motir auto --auto-approve-replan`). It adds a section telling the agent that a correction kept to its own card and that card’s siblings may be approved unattended, while anything wider — anchored at a container, or at nothing — goes to a person and stops the run, and that BOTH are legitimate. ⚠️ It changes the TEXT only: the agent’s tools, anchor and single submit are identical either way, and the bound on what may be approved is the LOOP’s, enforced over the plan that comes back. Absent means no, and it is ignored when `findingsPolicy` disables `replan` — approving a plan the agent was told not to submit is not a lane. |
| continueFrom string · optional | query | The id of a DEAD run of this item to CONTINUE (MOTIR-6531, `motir continue`). The prompt then carries a CONTINUE block — how that run ended, when it was last heard from, who ran it, its branch and its open pull request — and a git workflow that CHECKS THAT BRANCH OUT instead of cutting one. The branch is the item’s open pull request’s head, else the one the run recorded on `checkout_ready`. A run that is still open, that succeeded, that holds no leg for this item or that belongs to another workspace is `CONTINUE_FROM_INVALID` (422). |
Esempio
curl
curl -X GET 'https://app.motir.co/api/v1/work-items/{key}/dispatch-prompt' \
-H 'Authorization: Bearer $MOTIR_TOKEN'Risposte
| Stato | Condizione |
|---|---|
| 200 | The assembled prompt and the facts a client routes on. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| key required | string | |
| prompt required | string | |
| parentKey required | string | null | |
| targetRepo required | string | null | |
| targetRepoCloneUrl required | string | null | |
| targetRepoDefaultBranch required | string | null | |
| targetRepos required | object[] | |
| workflowMode required | string per_item_pr · session_lineage | |
| sessionBranch required | string | null | |
| workBranch optional | string | null | |
| branch optional | string | null | |
| advisories required | object[] |
Submit an AI expansion of a container work item
Submit an AI expansion of one CONTAINER work item (epic / story / task / bug): the planner drafts the children it should have. Returns `202` with `{ jobId, planId, statusUrl }` the moment the job is ACCEPTED — it does not wait for the planner, and the body carries no result because there is none yet. ⚠️ IMPORTANT: this does NOT create work items. The job produces a PLAN of proposals, and approving that plan in Motir is the only thing that turns a proposal into a work item. Do not report expanded children as created. ⚠️ A submit SPENDS the token owner’s AI credits, so wrapping this call in a blind retry-on-timeout costs real money — poll `statusUrl` instead of resubmitting. A leaf (subtask) cannot be expanded. Requires the `ai:plan` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| key string · required | path | The container work item’s `MOTIR-<n>` key (case-insensitive). |
Esempio
curl
curl -X POST 'https://app.motir.co/api/v1/work-items/{key}/expansions' \
-H 'Authorization: Bearer $MOTIR_TOKEN'Risposte
| Stato | Condizione |
|---|---|
| 202 | The job was accepted. Nothing has been planned yet — poll `statusUrl` for the outcome. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 402 | A plan entitlement is exhausted — the workspace owner’s AI credits, or the organization’s total attachment-storage cap. The request was valid; it was refused for want of headroom, and retrying will not help until the limit is lifted. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
| 503 | A dependency this operation needs — the motir-ai planning service — could not be reached or is misconfigured. The request itself was fine; retrying later is the right response. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| jobId required | string | |
| planId required | string | |
| statusUrl required | string |
Get a run target’s current How to test
The CURRENT How-to-test record on a work item — the one the newest run published onto its run target: its rich-text Markdown body (sections, commands in fenced code blocks), the preview path, and a section per repository with its commit. `repos` may be EMPTY: a person writing from the item page names no repository, because Motir derives them from the linked pull requests. `record` is `null` when no run has written one. A CLI renders it into the `## How to test` section of each session pull request body, so the body and the item page show one record. A read. Requires the `project:browse` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| key string · required | path | The work item key, e.g. `ACME-7`. |
Esempio
curl
curl -X GET 'https://app.motir.co/api/v1/work-items/{key}/how-to-test' \
-H 'Authorization: Bearer $MOTIR_TOKEN'Risposte
| Stato | Condizione |
|---|---|
| 200 | The current record, or `record: null`. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| key required | string | |
| record required | object | null |
Record what BUILT a work item
Record implementation provenance — the harness and model an agent ran as, and whether the run was `byok` or `manual` — WITHOUT asserting anything about where the work is integrated. Use this on the per-item pull-request path, where there is no session branch to report; use `POST …/integration` when there is one. It moves NO status and leaves the item’s session branch untouched, both of which are echoed back so a client can see it. A field you omit is left exactly as it is — omitting all of them changes nothing. Requires the `work_item:edit` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| key string · required | path | The work item’s `MOTIR-<n>` key (case-insensitive). |
Corpo della richiesta
The provenance to record. `sessionBranch` is NOT accepted here — send it to `POST …/integration`, which is the operation that asserts integration.
| Property | Type | Description |
|---|---|---|
| implementationSource optional | string byok · manual | |
| implementationHarness optional | string | |
| implementationModel optional | string |
Esempio
curl
curl -X POST 'https://app.motir.co/api/v1/work-items/{key}/implementation' \
-H 'Authorization: Bearer $MOTIR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{}'Risposte
| Stato | Condizione |
|---|---|
| 200 | The item’s recorded provenance, with its unchanged status and branch. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| key required | string | |
| status required | string | |
| sessionBranch required | string | null | |
| updatedAt required | string | |
| implementationSource required | string | null byok · manual | |
| implementationHarness required | string | null | |
| implementationModel required | string | null |
Record a work item as integrated on a session branch
Record that a work item’s work has been integrated onto a session branch: it moves to “In review” and records the branch, in ONE transaction, which unblocks its dependents while the session pull request awaits a human merge. Optionally self-report the implementation harness and model (`implementationSource` defaults to `byok`); omit all three to leave the item’s recorded provenance untouched. Honors the project’s workflow rules — an item with no legal path to “In review” is refused and its branch is left unchanged. Requires the `work_item:edit` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| key string · required | path | The work item’s `MOTIR-<n>` key (case-insensitive). |
Corpo della richiesta
The session branch the work was integrated onto, and optional provenance.
| Property | Type | Description |
|---|---|---|
| sessionBranch required | string | |
| implementationSource optional | string byok · manual | |
| implementationHarness optional | string | |
| implementationModel optional | string |
Esempio
curl
curl -X POST 'https://app.motir.co/api/v1/work-items/{key}/integration' \
-H 'Authorization: Bearer $MOTIR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"sessionBranch":"<sessionBranch>"}'Risposte
| Stato | Condizione |
|---|---|
| 200 | The item’s new status, its recorded branch and its provenance. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| key required | string | |
| status required | string | |
| sessionBranch required | string | null | |
| updatedAt required | string | |
| implementationSource required | string | null byok · manual | |
| implementationHarness required | string | null | |
| implementationModel required | string | null |
Remove a relationship edge
Remove the edge named by its ENDPOINTS — the same pair that created it. Idempotent: 204 whether or not an edge was there, because the post-condition holds either way. Requires the `work_item:edit` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| key string · required | path | The work item’s `MOTIR-<n>` key. Never its internal id (ADR §7). |
| toKey string · required | query | The other endpoint’s key. |
| relationship string · required | query | The relationship to remove. |
Esempio
curl
curl -X DELETE 'https://app.motir.co/api/v1/work-items/{key}/links' \
-H 'Authorization: Bearer $MOTIR_TOKEN'Risposte
| Stato | Condizione |
|---|---|
| 204 | The edge does not exist. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Read a work item’s relationship edges
All seven edge groups. An empty group is `[]`, never an absent key — to a typed client those are different things. `supersedes` lists the older work items this one replaces; `supersededBy` the newer ones that replace it. Requires the `project:browse` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| key string · required | path | The work item’s `MOTIR-<n>` key. Never its internal id (ADR §7). |
Esempio
curl
curl -X GET 'https://app.motir.co/api/v1/work-items/{key}/links' \
-H 'Authorization: Bearer $MOTIR_TOKEN'Risposte
| Stato | Condizione |
|---|---|
| 200 | The seven edge groups. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| blockedBy required | object[] | |
| blocks required | object[] | |
| relatesTo required | object[] | |
| duplicates required | object[] | |
| clones required | object[] | |
| supersedes required | object[] | |
| supersededBy required | object[] |
Create a relationship edge
Link this work item to another by key. Creating an edge that already exists is a 409 — the body is valid, the state is not what the request assumed. `supersedes` records that this (newer) work item replaces `toKey`; `superseded_by` writes the same edge from the older end. Neither gates readiness — only `blocked_by` / `blocks` do. Requires the `work_item:edit` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| key string · required | path | The work item’s `MOTIR-<n>` key. Never its internal id (ADR §7). |
Corpo della richiesta
The other endpoint and the relationship.
| Property | Type | Description |
|---|---|---|
| toKey required | string | |
| relationship required | string blocked_by · blocks · relates_to · duplicates · clones · supersedes · superseded_by |
Esempio
curl
curl -X POST 'https://app.motir.co/api/v1/work-items/{key}/links' \
-H 'Authorization: Bearer $MOTIR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"toKey":"<toKey>","relationship":"blocked_by"}'Risposte
| Stato | Condizione |
|---|---|
| 201 | The created edge. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 409 | The request conflicts with existing state. The body is well-formed; the state is not what the request assumed. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| toKey required | string | |
| relationship required | string blocked_by · blocks · relates_to · duplicates · clones · supersedes · superseded_by |
Read the plan this work item produced, without deciding it
READ the plan a work item’s own re-plan produced — the proposals, BEFORE anybody decides on them. Resolved by exactly the walk `approveWorkItemPlan` uses (the planning conversation ANCHORED at this key → its last submitted job → that job’s plan), so what this returns is the plan that POST would approve, and there is no way to name a plan the item did not produce. ⚠️ It DECIDES NOTHING: no status moves, no proposal materializes, and the plan is left exactly where it was — a plan still `generating` comes back with the proposals that have arrived so far. It exists for an unattended loop that must BOUND what it is about to approve: `motir auto --auto-approve-replan` reads the proposals, checks that every one of them falls inside the card’s own lane, and declines to approve one that does not. ⚠️ `plan` IS `null` WHEN NOTHING IS ANCHORED HERE, which is the ordinary state of almost every card and is an ANSWER rather than an error — it is how a caller learns that a re-plan was anchored somewhere else, at a container or at nothing. ⚠️ READING IS `project:browse`, and only DECIDING is `ai:decide_plan`: the plan inside the wrapper is exactly the document `GET /api/v1/plans/{planId}` already returns to the same audience, addressed by the card instead of by an id its caller has no way to learn. The bound worth having is on the decision, and that one is untouched. Requires the `project:browse` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| key string · required | path | The work item whose plan is read (case-insensitive). |
Esempio
curl
curl -X GET 'https://app.motir.co/api/v1/work-items/{key}/plan-approval' \
-H 'Authorization: Bearer $MOTIR_TOKEN'Risposte
| Stato | Condizione |
|---|---|
| 200 | `plan` carries the plan and its proposals, in the same shape `GET /api/v1/plans/{planId}` returns — or `null` when no plan is anchored at this card. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| plan required | object | null |
Approve the plan this work item produced
APPROVE the plan a work item’s own re-plan produced, without a browser session — the entrance `motir auto --auto-approve-replan` drives. Its proposals become work items: an `add` creates, a `modify` applies to the same item, a `remove` archives. ⚠️ IT IS ADDRESSED BY THE WORK ITEM, and that is the bound: the server resolves the plan from the planning conversation ANCHORED at this key, so there is no way to name a plan the item did not produce. Every other plan — a cadence plan, an onboarding generation, one submitted from the project-wide panel — is refused here and keeps the human decision it was written under. It calls the same service the in-app approve does, so the confirmation gate, the re-validation and the one-shot concurrency guard are identical; a plan that has already been approved or declined answers 409, exactly as it does in the app. Requires the `ai:decide_plan` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| key string · required | path | The work item whose plan is approved (case-insensitive). |
Esempio
curl
curl -X POST 'https://app.motir.co/api/v1/work-items/{key}/plan-approval' \
-H 'Authorization: Bearer $MOTIR_TOKEN'Risposte
| Stato | Condizione |
|---|---|
| 200 | The approved plan and its proposals, each now carrying the `workItemKey` it materialized into. The plan’s own id is on the body, which is how a caller that never knew it can report what was approved. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 409 | The request conflicts with existing state. The body is well-formed; the state is not what the request assumed. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| id required | string | |
| status required | string generating · planned · stale · approved · declined | |
| origin required | string user · cadence | |
| title required | string | null | |
| summary required | string | null | |
| sourceJobId required | string | null | |
| proposalCount required | integer | |
| createdAt required | string | |
| plannedAt required | string | null | |
| decidedAt required | string | null | |
| proposals required | object[] |
Declare which pull request delivers this work item
DECLARE that a pull request delivers this work item — the only thing that associates the two. There is no title parse and no branch fallback: a pull request nobody links moves no work item when it merges, and carries a failing check saying so. ⚠️ IT ADDS, IT DOES NOT MOVE. The link is a DELIVERY ROW, and calling this again naming a DIFFERENT work item leaves the first delivery exactly where it was and writes a second. Both directions are expressible — many pull requests to one work item (which is what holds a part-delivered one open) and one pull request to many work items — so the response reports no moved-from, because nothing moves. Address the pull request as `repository` + `number`, or as the `url` `gh pr create` printed; give both and they must AGREE. It works BEFORE any webhook delivery has arrived — the case it exists for — writing the row from the `headRef` / `baseRef` / `title` supplied, and `created` says whether it did. A later delivery refreshes those and leaves the links alone. Requires the `work_item:edit` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| key string · required | path | The work item’s `MOTIR-<n>` key. Never its internal id (ADR §7). |
Corpo della richiesta
The pull request’s address, and the refs the row is seeded from.
| Property | Type | Description |
|---|---|---|
| repository optional | string | |
| number optional | integer | |
| url optional | string | |
| headRef required | string | |
| baseRef required | string | |
| title optional | string |
Esempio
curl
curl -X POST 'https://app.motir.co/api/v1/work-items/{key}/pull-requests' \
-H 'Authorization: Bearer $MOTIR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"headRef":"<headRef>","baseRef":"<baseRef>"}'Risposte
| Stato | Condizione |
|---|---|
| 200 | The work item, the pull request the link resolved to, and whether this call created its row. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| key required | string | |
| created required | boolean | |
| pullRequest required | object |
Claim the repair of a work item’s failing or ejected pull requests
Hand a work item’s RED pull requests to ONE fixing agent, after the run that opened them has ended (`motir fix <key>`): an `implemented` item, or an `in_review` one the merge queue threw out for a reason a CODE CHANGE could answer. In ONE transaction the item’s row is locked and, in order: an archived item, or one at neither the Implemented nor the In Review status, is `not_repairable` (`not_implemented`); an In Review item with no merge-queue outcome standing at a pull request’s current head is `not_failing`; an In Review item whose standing outcome is one no code change fixes — a repository setting, or a hand removal from the queue — is `repair_not_code`; an item whose pull requests belong to a run launched against another item is `not_repairable` (`repair_on_run_target`, naming `runTargetKey`); an item with no pull requests is `no_pull_requests`; an item with no failing OPEN pull request is `ci_running` when one is running, else `not_failing`. A pull request the merge queue threw out for a failure that still stands at its current head counts as FAILING whatever its own checks say, and carries `queueExit` (the reason and the queue’s failing check). Otherwise an open dispatch run with command `fix` already holding the item answers `mine` (yours — a resumed repair, same `runId`) or `taken` (somebody else’s, named with its start), and if there is none a `fix` run is opened: `claimed`. ⚠️ A refusal is a 200 with an `outcome`, not an error. ⚠️ The item’s status and assignee are NEVER written: the open run is the lock, and the build moves the item when it goes green. CLOSE the run (`closeDispatchRun`) when the repair ends. Requires the `work_item:edit` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| key string · required | path | The work item’s `MOTIR-<n>` key (case-insensitive). |
Esempio
curl
curl -X POST 'https://app.motir.co/api/v1/work-items/{key}/repair' \
-H 'Authorization: Bearer $MOTIR_TOKEN'Risposte
| Stato | Condizione |
|---|---|
| 200 | What the repair claim resolved to, the `fix` run, and the failing pull requests. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| key required | string | |
| title required | string | |
| outcome required | string claimed · mine · taken · not_repairable | |
| reason required | string | null not_implemented · repair_on_run_target · no_pull_requests · ci_running · not_failing · repair_not_code | |
| runTargetKey required | string | null | |
| runId required | string | null | |
| holder required | object | null | |
| startedAt required | string (date-time) | null | |
| repairClass required | string ci · acceptance_rerun · review | |
| acceptanceRefusal required | object | null | |
| reviewRefusal required | object | null | |
| pullRequests required | object[] |
Restore an archived work item
The inverse of archiving. Idempotent on an item that is not archived. Requires the `work_item:archive` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| key string · required | path | The work item’s `MOTIR-<n>` key. Never its internal id (ADR §7). |
Esempio
curl
curl -X POST 'https://app.motir.co/api/v1/work-items/{key}/restore' \
-H 'Authorization: Bearer $MOTIR_TOKEN'Risposte
| Stato | Condizione |
|---|---|
| 200 | The restored work item. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| key required | string | |
| kind required | string epic · story · task · subtask · bug | |
| type required | string | null code · design · test · content · copy · translate · research · review · verification · decision · choice · deploy · manual · legal · chore | |
| title required | string | |
| status required | string | |
| priority required | string lowest · low · medium · high · highest | |
| assigneeId required | string | null | |
| reporterId required | string | |
| dueDate required | string (date-time) | null | |
| estimateMinutes required | integer | null | |
| storyPoints required | number | null | |
| createdAt required | string (date-time) | |
| updatedAt required | string (date-time) | |
| obsolescence required | string | null outdated · deprecated | |
| obsolescenceNoteMd required | string | null | |
| descriptionMd required | string | null | |
| parentKey required | string | null | |
| folderId required | string | null | |
| folderPath required | string[] | null | |
| ancestorKeys required | string[] | |
| children required | object[] | |
| links required | object | |
| readiness required | object | |
| labels required | object[] | |
| components required | object[] | |
| commentCount required | integer | |
| sprintId required | string | null | |
| targetRepo required | string | null | |
| targetRepos required | string[] | |
| targetRepositories required | object[] | |
| executor required | string | null coding_agent · human | |
| difficulty required | string | null trivial · low · medium · high | |
| planningSource required | string | null native · mcp · manual · api | |
| planningHarness required | string | null | |
| planningModel required | string | null | |
| implementationSource required | string | null hosted · byok · manual | |
| implementationHarness required | string | null | |
| implementationModel required | string | null | |
| archivedAt required | string (date-time) | null | |
| deliveries required | object[] |
Get the review prompt a hosted review run is handed
The server-assembled REVIEW prompt for a work item whose delivered code a hosted review run is judging: the card’s description and explanation, its acceptance criteria, its published How to test, and every pull request of its delivery set at the REVIEWED head — the version its `agent_review` gate names, never a later commit — with the instruction to push nothing, post nothing to GitHub, and submit ONE verdict. Answers ONLY the credential of a `review` dispatch run, for that run’s own card (`REVIEW_RUN_TOKEN_REQUIRED` / `DISPATCH_RUN_TOKEN_OUT_OF_SCOPE`, 403). A read. Requires the `project:browse` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| key string · required | path | The work item key, e.g. `ACME-7`. |
Esempio
curl
curl -X GET 'https://app.motir.co/api/v1/work-items/{key}/review-prompt' \
-H 'Authorization: Bearer $MOTIR_TOKEN'Risposte
| Stato | Condizione |
|---|---|
| 200 | The gate, the version under review, its pull requests and the prompt. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| key required | string | |
| gateId required | string | |
| subjectVersion required | string | |
| pullRequests required | object[] | |
| prompt required | string |
List the statuses a work item can move to
The workflow-legal targets from the item’s current status. An `open`-policy project permits every other status; a `restricted` one permits only the declared edges. Requires the `project:browse` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| key string · required | path | The work item’s `MOTIR-<n>` key. Never its internal id (ADR §7). |
Esempio
curl
curl -X GET 'https://app.motir.co/api/v1/work-items/{key}/transitions' \
-H 'Authorization: Bearer $MOTIR_TOKEN'Risposte
| Stato | Condizione |
|---|---|
| 200 | The legal transition targets. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| transitions required | object[] |
Move a work item to a new status
Apply a workflow transition. A status the workflow does not define and a status not reachable from here are DIFFERENT errors, because a client can fix only one of them. A MARKED item (`obsolescence` set) stays finished: a move to any status outside the done category is refused with `MARKED_CARD_CANNOT_REOPEN` (422, `mark`: the key, the mark and the refused target) until the mark is cleared with `updateWorkItem`; a move within the done category (`done` ↔ `cancelled`) is not. Requires the `work_item:edit` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| key string · required | path | The work item’s `MOTIR-<n>` key. Never its internal id (ADR §7). |
Corpo della richiesta
The target status key.
| Property | Type | Description |
|---|---|---|
| status required | string |
Esempio
curl
curl -X POST 'https://app.motir.co/api/v1/work-items/{key}/transitions' \
-H 'Authorization: Bearer $MOTIR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"status":"<status>"}'Risposte
| Stato | Condizione |
|---|---|
| 200 | The work item at its new status. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 404 | The resource does not exist, or it is outside the workspace this token is bound to — deliberately the same answer. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
| Property | Type | Description |
|---|---|---|
| key required | string | |
| kind required | string epic · story · task · subtask · bug | |
| type required | string | null code · design · test · content · copy · translate · research · review · verification · decision · choice · deploy · manual · legal · chore | |
| title required | string | |
| status required | string | |
| priority required | string lowest · low · medium · high · highest | |
| assigneeId required | string | null | |
| reporterId required | string | |
| dueDate required | string (date-time) | null | |
| estimateMinutes required | integer | null | |
| storyPoints required | number | null | |
| createdAt required | string (date-time) | |
| updatedAt required | string (date-time) | |
| obsolescence required | string | null outdated · deprecated | |
| obsolescenceNoteMd required | string | null | |
| descriptionMd required | string | null | |
| parentKey required | string | null | |
| folderId required | string | null | |
| folderPath required | string[] | null | |
| ancestorKeys required | string[] | |
| children required | object[] | |
| links required | object | |
| readiness required | object | |
| labels required | object[] | |
| components required | object[] | |
| commentCount required | integer | |
| sprintId required | string | null | |
| targetRepo required | string | null | |
| targetRepos required | string[] | |
| targetRepositories required | object[] | |
| executor required | string | null coding_agent · human | |
| difficulty required | string | null trivial · low · medium · high | |
| planningSource required | string | null native · mcp · manual · api | |
| planningHarness required | string | null | |
| planningModel required | string | null | |
| implementationSource required | string | null hosted · byok · manual | |
| implementationHarness required | string | null | |
| implementationModel required | string | null | |
| archivedAt required | string (date-time) | null | |
| deliveries required | object[] |
List the workspaces this token’s owner belongs to
A discovery read, and the ONE place v1 answers at the account level rather than the bound workspace: it returns the workspaces the token OWNER is a member of, so a client holding a fresh token can learn which workspace ids exist for it. Every resource endpoint stays scoped to the bound workspace. Requires the `project:browse` permission.
Richiesta
| Parameter | In | Description |
|---|---|---|
| cursor string · optional | query | An opaque page cursor from a previous response’s `nextCursor`. Omit for the first page. Cursors are signed and scoped to their own collection — one issued elsewhere is a 422, never a silent reset. |
| limit integer · optional | query | Rows per page. Defaults to 50; a larger value is CLAMPED to 100 rather than rejected. |
Esempio
curl
curl -X GET 'https://app.motir.co/api/v1/workspaces' \ -H 'Authorization: Bearer $MOTIR_TOKEN'
Risposte
| Stato | Condizione |
|---|---|
| 200 | A page of workspaces. |
| 401 | Authentication required. No token, or a token that is malformed, unknown, revoked or expired — the five are deliberately undifferentiated. |
| 403 | The token is valid but its granted scopes do not include the one this operation requires. |
| 422 | The request is malformed in a way the caller can fix: an invalid cursor, an out-of-range `limit`, a failed body validation. |
| 429 | The token's rate-limit budget for the current window is exhausted. Read `X-RateLimit-Reset` for when it refills. |
| 500 | An unexpected server fault. The body carries no `code`, no stack and no driver text. |
Schema della risposta
PageEnvelope & object