Motion API
Motion (usemotion.com) REST API の完全クライアント。タスク、プロジェクト、繰り返しタスク、コメント、カスタムフィールド、ユーザー、ワークスペース、ステータス、スケジュールの全27のドキュメント化されたエンドポイントに加え、まだドキュメント化されていないものに対するrawエスケープハッチを提供します。
このスキルを使用するタイミング
Motionに関連する作業(タスクの読み取りや作成、プロジェクトの一覧表示、タスクへのコメント投稿、繰り返しタスクの管理、ワークスペースのステータス、ユーザー、スケジュールの読み取り)を行う際に使用します。
認証情報
1つの認証情報MOTION_API_KEYを使用します。プラットフォームは、RunWithCredentials経由でスクリプトが実行されるときに、これを環境変数として注入します。キーをコマンドラインに渡したり、出力(print/echo)したりしないでください。
Motionの設定 → APIでキーを取得してください。Motionはキーを1回だけ表示するため、すぐにコピーしてください。呼び出しが401を返し始めた場合、キーが失効または再生成されたため、交換が必要です。
注意:プロキシモードの認証情報配信では、env varは非機密のプレースホルダを保持し、実際の値はapi.usemotion.comへのリクエストに注入されます。新しく保存された認証情報が伝播するまでに数分かかることがあります。すぐに401が発生し、その後成功する場合は、キーの問題ではなく伝播中であることを意味します。
APIが従う規約
これらの事実は、呼び出しが成功するか失敗するかの違いを生みます。
| 項目 | 値 |
|---|---|
| ベースURL | https://api.usemotion.com |
| 認証ヘッダー | X-API-Key: <key> |
| ヘルスチェック | GET /v1/users/me — キーの所有者を返す |
| パスプレフィックス | カスタムフィールド以外は/v1/、カスタムフィールドは/beta/ |
| レート制限 | 12リクエスト/分(個人)、最大120(チーム)、エンタープライズはそれ以上 |
| 説明文 | Github Flavored Markdown |
| 優先度の値 | ASAP、HIGH、MEDIUM、LOW(繰り返しタスク:HIGH/MEDIUMのみ) |
| 期限タイプ | HARD、SOFT、NONE(繰り返しタスク:HARD/SOFTのみ) |
ページネーション。 一覧系エンドポイントはcursorを受け取り、エンベロープを返します。
{ "meta": { "nextCursor": "…", "pageSize": 25 }, "tasks": [ … ] }
エンベロープのキーはmetaで、次ページフィールドはnextCursorです。一覧コマンドに--allを渡すと、スクリプトがカーソルを追跡し、1つのフラットな配列を返します。
--allは100ページで停止し、失敗します。これにより、暴走クエリが実行をハングさせることはありません。その場合はクエリを絞り込んでください。また、ページがJSONオブジェクトでも配列でもない場合(切り詰められた、またはゲートウェイエラーページ)、ウォークは拒否し、取得したものを返しません。完全に見える部分的なリストはエラーよりも悪いからです。
2つの一覧エンドポイントは、ページネーションなしで裸の配列を返します — GET /v1/statuses と GET /beta/workspaces/{id}/custom-fields。それ以外はすべてエンベロープ形式です。
エラー。 非2xx応答には{"message", "error", "statusCode"}が含まれます。スクリプトはそれをstderrにJSONで出力し、非零の終了コードで終了します。429はRetry-Afterを尊重してバックオフ付きで再試行されます。400、401、403、404は即座に表示され、再試行されません。バリデーションエラーにはフィールドごとのerrors[]配列が含まれます。クライアントはこれらのメッセージを追加するため、実際の理由が表示されます。
User-Agentは必須です。 Motionのエッジは、デフォルトのPython-urllib/x.yシグネチャをCloudflare 403(browser_signature_banned)でリクエストがAPIに到達する前にブロックします。クライアントは常にmotion-api-skill/1.0を送信します。このAPIに対して独自のリクエストを作成する場合は、明示的にUser-Agentを設定してください。設定しないと、実際の応答ではなく混乱を招く403を受け取ることになります。
ブール値のクエリパラメータは小文字でなければなりません。 Motionはクエリ文字列をテキストとして解析するため、ブールフィルターはリテラルのtrueである必要があり、PythonのTrueではありません。これを間違えても無視されるわけではありません。APIは反対の値について話す混乱を招く400で応答します(以下の実際の違いのセクションを参照)。クライアントは常に小文字のリテラルを送信します。
コマンドリファレンス
すべてのコマンドはJSONをstdoutに出力します。python3 motion.py <group> --helpでフラグを一覧表示します。
タスク
| コマンド | エンドポイント |
|---|---|
tasks list | GET /v1/tasks |
tasks get <task_id> | GET /v1/tasks/{id} |
tasks create | POST /v1/tasks |
tasks update <task_id> | PATCH /v1/tasks/{id} |
tasks move <task_id> --workspace-id … | PATCH /v1/tasks/{id}/move |
tasks delete <task_id> --yes | DELETE /v1/tasks/{id} |
tasks unassign <task_id> --yes | DELETE /v1/tasks/{id}/assignee |
tasks listのフィルター:--assignee-id、--project-id、--workspace-id、--name(大文字小文字を区別しない部分文字列)、--label(単数、1つのラベル名)、--status(繰り返し可能)、--include-all-statuses。--statusと--include-all-statusesは相互に排他的です — 両方を送信するとAPIに拒否され、CLIは保証された400にリクエストを費やす代わりに、そのメッセージで早期に拒否します。
--include-all-statusesは、ワークスペースのデフォルトを超えて移動したタスクを確認する方法です(例:BacklogやIn Reviewにあるすべてのタスク)。これがないと、GET /v1/tasksはデフォルトのステータスのみを返し、リストは空に見えます。--allと組み合わせると、すべてのステータスにわたって、誰かに割り当てられたすべてのタスクが返されます。
tasks create / tasks updateは同じフィールドを受け取ります:--name、--workspace-id、--due-date(ISO 8601;スケジュールされたタスクには必須)、--duration、--status、--project-id、--description、--priority、--label(繰り返し可能)、--assignee-id。
--durationは整数の分数、または文字列NONE / REMINDERを受け入れます(入力時に大文字小文字を区別せず、大文字に正規化されます)。
自動スケジューリングは3つの状態があり、その違いは重要です。
- 両方のフラグを省略 → フィールドはリクエストから完全に除外(変更なし)
--auto-scheduled '{"deadlineType":"HARD"}'→ 自動スケジューリングをオンにします。入れ子フィールドはstartDate、deadlineType(HARD/SOFT/NONE)、schedule(デフォルトWork Hours)です。--auto-scheduled-off→ 明示的にnullを送信し、自動スケジューリングをオフにします。
タスクのステータスで自動スケジューリングが有効になっている必要があります。そうでない場合、APIは拒否します。
タスクの応答は次のようになります — 以下のカレンダーワークフローが依存するフィールドにトリミングされています。
{
"id": "tsk_123",
"name": "Draft Q4 plan",
"description": "Outline the three scenarios.",
"status": { "name": "In Progress", "isResolvedStatus": false },
"priority": "HIGH",
"labels": ["q4"],
"assignees": [{ "id": "usr_1", "name": "Sam Patel" }],
"project": { "id": "prj_1", "name": "Planning" },
"dueDate": "2026-11-01T17:00:00.000Z",
"duration": 45,
"scheduledStart": "2026-10-28T09:00:00.000Z",
"startOn": "2026-10-28",
"completedTime": null
}
次のように読み取ってください:statusはオブジェクトであり、文字列ではありません。フィルターに使用するのはstatus.isResolvedStatusで、タスクが完了またはキャンセルされるとtrueになります。assigneesとlabelsは配列です。projectはオブジェクトまたはnullです。scheduledStartとdueDateはUTCです。startOnは単なる日付です。
カレンダーエンドポイントはありません。 カレンダー上のタスクの位置は、そのscheduledStart(UTC ISO 8601)、duration(分)、startOn(プレーンな日付)です。「次は何か」という質問に答えるには、タスクを取得し、scheduledStartで並べ替えます。イベントを返すスケジュールエンドポイントを探しに行かないでください。schedules listは勤務時間枠のみを返すためです(以下参照)。予約済みのアイテムは返しません。status.isResolvedStatusのタスクをフィルタリングして、完了またはキャンセルされたものを除外します。
プロジェクト
| コマンド | エンドポイント |
|---|---|
projects list [--workspace-id …] | GET /v1/projects |
projects get <project_id> | GET /v1/projects/{id} |
projects create --name … --workspace-id … | POST /v1/projects |
projects createは、--due-date、--description(HTML受け入れ可能)、--label(繰り返し可能)、--priority(サーバーサイドでデフォルトはMEDIUM)、テンプレートペア--project-definition-id + --stagesも受け取ります。ステージは定義の順序と数に正確に一致する必要があります。一致しない場合、APIは400を返します。長いステージ配列の場合は--stages @stages.jsonを使用してください。CLIはペアを強制しません。定義IDなしでステージリストを送信するとAPIは拒否します。
繰り返しタスク
| コマンド | エンドポイント |
|---|---|
recurring-tasks list --workspace-id … | GET /v1/recurring-tasks |
recurring-tasks create | POST /v1/recurring-tasks |
recurring-tasks delete <id> --yes | DELETE /v1/recurring-tasks/{id} |
recurring-tasks createには--frequency、--name、--workspace-id、--assignee-idが必要です。オプション:--deadline-type(HARD/SOFTのみ — NONEはここでは拒否されます)、--duration(整数の分数、またはREMINDER。NONEはこのエンドポイントでは受け入れられません)、--starting-on、--ideal-time(HH:mm)、--schedule(デフォルトWork Hours)、--description、--priority(HIGH/MEDIUMのみ)。
--starting-onはAPIにそのまま送信されます — トリミングは行われません。
--frequencyはオブジェクトではなく、Motion独自のDSLの文字列です。 これはよく間違えられます。語彙はhttps://docs.usemotion.com/cookbooks/frequency/で文書化されています。
- 曜日コード:
MO、TU、WE、TH、FR、SA、SU - 毎日:
daily_every_day、daily_every_week_day、daily_specific_days_[MO, TU, FR] - 毎週:
weekly_any_day、weekly_any_week_day、weekly_specific_days_[MO, FR] - 隔週:
biweekly_first_week_any_day、biweekly_first_week_specific_days_[MO, TU]、biweekly_second_week_any_week_day - 月ごと(曜日指定):
monthly_first_MO、monthly_second_TU、monthly_last_FR - 月ごと(日付指定):
monthly_1、monthly_15、monthly_31(範囲外の日は月の最終日にフォールバック) - 月ごと(週指定):
monthly_any_day_first_week、monthly_any_week_day_last_week、monthly_last_day_of_month - 四半期ごと:
quarterly_first_day、quarterly_last_week_day、quarterly_any_day_first_week
曜日の配列は常に頻度タイプとペアにする必要があります — [MO, FR]だけでは無効です。
頻度文字列が難しい部分なので、完全な呼び出し例を示します。
python3 motion.py recurring-tasks create \
--name "Weekly review" --workspace-id ws_1 --assignee-id usr_1 \
--frequency "weekly_specific_days_[MO,FR]" \
--deadline-type SOFT --duration 30 --ideal-time 16:00 \
--schedule "Work Hours" --priority MEDIUM
ここでのより狭いセットに注意してください。繰り返しタスクはHIGH/MEDIUMの優先度とHARD/SOFTの期限タイプのみを受け取ります。一方、APIの他の部分ではASAP/LOWとNONEも許可されています。CLIは両方を強制します。
コメント
| コマンド | エンドポイント |
|---|---|
comments list --task-id … | GET /v1/comments |
comments create --task-id … [--content …] | POST /v1/comments |
--contentは入力時にGithub Flavored Markdownであり、HTMLとして返されます。これはオプションです — --contentがない場合、リクエストボディにはタスクIDのみが含まれます。Motionが空のコメントを受け入れるかどうかは確認されていません。何かを言いたい場合は--contentを渡してください。
カスタムフィールド — /beta/プレフィックスに注意
| コマンド | エンドポイント |
|---|---|
custom-fields list --workspace-id … | GET /beta/workspaces/{workspaceId}/custom-fields |
custom-fields create --workspace-id … --type … --name … | POST /beta/workspaces/{workspaceId}/custom-fields |
custom-fields delete <id> --workspace-id … --yes | DELETE /beta/workspaces/{workspaceId}/custom-fields/{id} |
custom-field-values add-task <task_id> … | POST /beta/custom-field-values/task/{taskId} |
custom-field-values add-project <project_id> … | POST /beta/custom-field-values/project/{projectId} |
custom-field-values delete-from-task <task_id> <value_id> --yes | DELETE /beta/custom-field-values/task/{taskId}/custom-fields/{valueId} |
custom-field-values delete-from-project <project_id> <value_id> --yes | DELETE /beta/custom-field-values/project/{projectId}/custom-fields/{valueId} |
12のカスタムフィールドタイプは、次のように正確に記述されます:text、url、date、person、multiPerson、phone、select、multiSelect、number、email、checkbox、relatedTo。
値を設定するには、--custom-field-instance-id(ワークスペース上のフィールド)、--type、--valueが必要です。--valueはJSONなので、正しく引用符で囲みます。文字列の場合は--value '"hello"'、数値の場合は--value '42'、マルチセレクトの場合は--value '["A","B"]'、チェックボックスの場合は--value 'true'のようにします。
python3 motion.py custom-field-values add-task tsk_123 \
--custom-field-instance-id cfi_1 --type select --value '"Alpha"'
custom-fields createは、--metadataをJSONとして受け取ります — format(plain/formatted/percent、数値のみ)、チェックボックス用のtoggle、セレクトフィールド用のoptions。
--metadata '{"options":[{"id":"a","value":"Alpha","color":"#ff0000"}]}'
ユーザー、ワークスペース、ステータス、スケジュール
| コマンド | エンドポイント |
|---|---|
me | GET /v1/users/me |
users list [--workspace-id …] [--team-id …] | GET /v1/users |
workspaces list [--ids …] | GET /v1/workspaces |
statuses list [--workspace-id …] | GET /v1/statuses |
schedules list | GET /v1/schedules |
workspaces list --idsは繰り返し可能で、--allはカーソルを追跡します。
users listはスコープを指定する必要があります。 --workspace-idまたは--team-idを渡してください。スクリプトはスコープなしの呼び出しを早期に拒否し、APIが返す400にリクエストを費やす代わりにそのメッセージを表示します。全ユーザーを一覧表示するには、ワークスペースをループします。
schedules listは勤務時間枠を返します。イベントではありません。 各スケジュールには独自のtimezoneと、曜日ごとの{start, end}ブロックの配列があります。また、1日に複数のブロックが含まれる場合があります(例:朝と夕方の2つのブロックを持つ学校の送り迎えスケジュール)。これを「ユーザーには1日1つの作業ブロックがある」と読んだり、カレンダーとして扱ったりしないでください。実際に予約されているものについては、タスクのscheduledStart値を読んでください。
Rawエスケープハッチ
python3 motion.py raw GET /v1/tasks --query status=Todo --query status=Doing
python3 motion.py raw GET /v1/tasks --query assigneeId=u1 --query includeAllStatuses=true --all
python3 motion.py raw POST /v1/comments --body '{"taskId":"t1","content":"hi"}'
python3 motion.py raw DELETE /v1/tasks/t1 --yes
名前付きコマンドがカバーしないものにはこれを使用します。--query KEY=VALUEは繰り返し可能で、同じキーを繰り返すと配列になります。--bodyはJSON文字列を受け取ります(または、ファイルから読み取る場合は@file.json)。--yesはDELETEリクエストに必要です(テストと偶発的な削除を防ぐため)。rawはエンベロープをそのまま返すか、--allが指定されている場合はカーソルを自動的に追跡します。
実際の違い:ライブドキュメントとこのクライアント
Motionはドキュメントでここで説明したほとんどのことに同意していますが、いくつかの違いと微妙な点があります。
401は伝播の遅延を意味する場合があります
認証情報画面でキーを生成した直後は、有効なキーであってもGET /v1/users/meが401を返すことがあります。これはキーが無効であることを意味するのではなく、APIゲートウェイが新しいキーを認識するのに最大で15分かかることを意味します。新しいキーをテストするときは、単純な呼び出しで500を待ってから、応答を信頼してください。これは一般的な問題であり、Motionは現在この遅延を認識しています。
このクライアントは401を再試行しません。待ってから再試行するのは、このスキルを使用するAIエージェント次第です。
カスタムフィールドのドキュメントは /v1/ を参照している
MotionのAPIドキュメントの例では、カスタムフィールドのエンドポイントが/v1/プレフィックスを使用しているように見えます。これは間違いです。実際のルートは/beta/です。これはバグなのか意図的なものなのかは不明ですが、このクライアントは/beta/にヒットします。
ブール値クエリパラメータの間違った大文字小文字は、反対の値を示す400を返す
もちろん、ブール値クエリパラメータは小文字でなければなりません。しかし、True(Pythonのデフォルト)を送信した場合、APIは**「値が無効です。有効な値はtrueです」**ではなく、次のような応答を返すかもしれません。
{"message": "Invalid request", "error": "'includeAllStatuses' should be 'false'."}
つまり、実際の間違いは大文字小文字ですが、エラーメッセージは渡すべき値がfalseであると示唆しています。これは誤解を招くため、クライアントはこのようなことが起こらないようにしています。
GET /v1/statuses は生のリストを返す
このエンドポイントは、エンベロープではなく、生のstatusオブジェクトの配列を返します。GET /v1/statusesに対するこのクライアントの応答はラップされません。
DELETEの応答
DELETE /v1/tasks/{id}の応答ボディは空です。このクライアントは204ステータスコードと空のボディを受け取ると、{}を出力します。
コマンドはバリデーションのためにAPIを信頼しない
このクライアントは、タスクの作成または更新の前に--project-idが有効かどうかを確認するためにprojects getを呼び出しません。それはAPIの仕事です。このクライアントは、引数が期待される型であることを確認する構文解析レベルのバリデーションのみを行い、意味的な正確性はMotionに委ねます。
開発者メモ
このスキルはSKILL.mdから直接生成されたシングルファイルPythonクライアントです。motion.pyがエントリポイントで、argparseベースのCLIを介して上記の各エンドポイントを公開します。
テスト: tests/ディレクトリには、各エンドポイントの基本的な正常系テストを含むtest_motion.pyが含まれています。pytest tests/で実行します。テストは実際のAPIキーを読み取るため、MOTION_API_KEYが設定されていることを確認してください。
貢献: 新しいエンドポイントのサポートを追加する場合は、以下を行ってください。
- エンドポイントとメソッドを確認します。
motion.pyに関数を追加します。- 関数を適切なグループに登録します。
- コマンドテーブルとドキュメントを更新します。
- テストを追加します。
rawエンドポイントは、クライアントに変更を加えずに、新しいAPI機能のプロトタイピングに使用できます。