API do Motion
Cliente completo para a API REST do Motion (usemotion.com) — todos os 27 endpoints documentados para
tarefas, projetos, tarefas recorrentes, comentários, campos personalizados, usuários, espaços de trabalho, status e
agendas, além de uma rota de escape raw para qualquer coisa ainda não documentada.
Quando usar esta skill
Use-a sempre que o trabalho envolver o Motion: ler ou criar tarefas, listar projetos, postar comentários em tarefas, gerenciar tarefas recorrentes ou consultar status do espaço de trabalho, usuários e agendas.
Credenciais
Uma credencial, MOTION_API_KEY. A plataforma a injeta como uma variável de ambiente quando
um script é executado via RunWithCredentials — nunca passe uma chave na linha de comando e nunca
a imprima ou exiba.
Obtenha uma chave no Motion em Configurações → API. O Motion a exibe apenas uma vez, então copie-a imediatamente. Se as chamadas começarem a retornar 401, a chave foi revogada ou regenerada e precisa ser substituída.
Nota: com a entrega de credencial em modo proxy, a variável de ambiente contém um espaço reservado não secreto e
o valor real é injetado nas requisições para api.usemotion.com. Uma credencial recém-salva pode
levar alguns minutos para propagar; um 401 imediato que depois dá certo é propagação, não chave inválida.
Convenções que a API segue
Estes são os fatos que fazem a diferença entre uma chamada que funciona e uma que não funciona.
| Coisa | Valor |
|---|---|
| URL Base | https://api.usemotion.com |
| Cabeçalho de Auth | X-API-Key: <chave> |
| Health check | GET /v1/users/me — retorna o proprietário da chave |
| Prefixos de caminho | /v1/ para tudo, exceto campos personalizados, que são /beta/ |
| Limites de taxa | 12 requisições/min (individual), até 120 (equipes), mais alto no enterprise |
| Descrições | Github Flavored Markdown |
| Valores de prioridade | ASAP, HIGH, MEDIUM, LOW (tarefas recorrentes: apenas HIGH/MEDIUM) |
| Tipos de prazo | HARD, SOFT, NONE (tarefas recorrentes: apenas HARD/SOFT) |
Paginação. Endpoints de lista recebem um cursor e retornam um envelope:
{ "meta": { "nextCursor": "…", "pageSize": 25 }, "tasks": [ … ] }
A chave do envelope é meta, e o campo da próxima página é nextCursor. Passe --all em qualquer
comando de lista e o script segue o cursor para você, retornando um único array achatado.
--all para em 100 páginas e então falha, para que uma consulta descontrolada não possa travar uma execução — restrinja
a consulta se você vir isso. E se uma página não for nem um objeto JSON nem um array (truncada ou
uma página de erro de gateway), a caminhada recusa em vez de retornar o que tem: uma lista
parcial que parece completa é pior do que um erro.
Dois endpoints de lista retornam um array simples sem paginação — GET /v1/statuses e
GET /beta/workspaces/{id}/custom-fields. Todo o resto é envelopado.
Erros. Respostas não-2xx carregam {"message", "error", "statusCode"}. Os scripts imprimem
isso como JSON no stderr e saem com código diferente de zero. 429 é repetido com backoff respeitando
Retry-After; 400, 401, 403 e 404 são exibidos imediatamente e nunca repetidos.
Falhas de validação carregam um array errors[] por campo; o cliente anexa essas mensagens
para que o motivo real fique visível.
Um User-Agent é obrigatório. O edge do Motion bloqueia a assinatura padrão Python-urllib/x.y
com um Cloudflare 403 (browser_signature_banned) antes que a requisição chegue à
API. O cliente sempre envia motion-api-skill/1.0; se você escrever sua própria requisição contra
esta API, defina um User-Agent explicitamente ou você receberá um 403 confuso em vez de uma resposta
real.
Parâmetros de consulta booleanos devem estar em minúsculas. O Motion analisa strings de consulta como texto, então um
filtro booleano deve ser o literal true, não o True do Python. Errar isso não é
um no-op silencioso: a API responde com um 400 confuso que fala sobre o valor oposto
(veja a seção de diferenças reais abaixo). O cliente sempre envia literais minúsculos.
Referência de comandos
Cada comando imprime JSON no stdout. python3 motion.py <grupo> --help lista as flags.
Tarefas
| Comando | Endpoint |
|---|---|
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 |
Filtros de tasks list: --assignee-id, --project-id, --workspace-id, --name
(substring sem diferenciar maiúsculas/minúsculas), --label (singular, um nome de rótulo), --status (repetível),
--include-all-statuses. --status e --include-all-statuses são mutuamente exclusivos —
enviar ambos é rejeitado pela API, e a CLI recusa antecipadamente com essa mensagem em vez de
gastar uma requisição em um 400 garantido.
--include-all-statuses é a maneira de ver uma tarefa que passou do padrão do espaço de trabalho
(por exemplo, tudo que está em Backlog ou Em Revisão); sem ele, GET /v1/tasks retorna
apenas o status padrão e a lista parece vazia. Combiná-lo com --all retorna todas
as tarefas atribuídas a alguém, em todos os status.
tasks create / tasks update aceitam os mesmos campos: --name, --workspace-id,
--due-date (ISO 8601; obrigatório para tarefas agendadas), --duration, --status,
--project-id, --description, --priority, --label (repetível), --assignee-id.
--duration aceita um número inteiro de minutos ou as strings NONE / REMINDER
(sem diferenciar maiúsculas/minúsculas na entrada, normalizado para maiúsculas).
O auto-agendamento é triestado, e a distinção é importante:
- omitir ambas as flags → o campo é deixado de fora da requisição completamente (inalterado)
--auto-scheduled '{"deadlineType":"HARD"}'→ ativa **-**o; campos aninhados sãostartDate,deadlineType(HARD/SOFT/NONE) eschedule(padrãoWork Hours)--auto-scheduled-off→ envia umnullexplícito, desativando-o
O status da tarefa deve ter auto-agendamento ativado, ou a API recusa.
Uma resposta de tarefa se parece com isto — reduzida aos campos dos quais o fluxo de trabalho de calendário abaixo depende:
{
"id": "tsk_123",
"name": "Rascunhar plano Q4",
"description": "Esboçar os três cenários.",
"status": { "name": "Em Andamento", "isResolvedStatus": false },
"priority": "HIGH",
"labels": ["q4"],
"assignees": [{ "id": "usr_1", "name": "Sam Patel" }],
"project": { "id": "prj_1", "name": "Planejamento" },
"dueDate": "2026-11-01T17:00:00.000Z",
"duration": 45,
"scheduledStart": "2026-10-28T09:00:00.000Z",
"startOn": "2026-10-28",
"completedTime": null
}
Leia como: status é um objeto, não uma string — o filtro que você quer é
status.isResolvedStatus, que se torna true quando uma tarefa é concluída ou cancelada.
assignees e labels são arrays; project é um objeto ou null. scheduledStart
e dueDate estão em UTC; startOn é uma data simples.
Não há endpoint de calendário. A posição de uma tarefa no calendário é seu scheduledStart
(UTC ISO 8601), com duration em minutos e startOn como a data simples. Para responder "o que
vem a seguir", busque as tarefas e ordene por scheduledStart — não vá procurar um endpoint de agenda
que retorne eventos, porque schedules list retorna apenas as janelas de horário comercial
(veja abaixo), não os itens agendados. Filtre tarefas com status.isResolvedStatus para
excluir qualquer coisa já concluída ou cancelada.
Projetos
| Comando | Endpoint |
|---|---|
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 também aceita --due-date, --description (HTML é aceito), --label
(repetível), --priority (padrão é MEDIUM no servidor) e o par de modelo
--project-definition-id + --stages. Os estágios devem corresponder exatamente à ordem e quantidade
da definição, ou a API retorna 400. Para arrays longos de estágios, use --stages @stages.json.
A CLI não impõe o par; a API rejeita uma lista de estágios sem um id de definição.
Tarefas recorrentes
| Comando | Endpoint |
|---|---|
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 requer --frequency, --name, --workspace-id e
--assignee-id. Opcionais: --deadline-type (apenas HARD/SOFT — NONE é rejeitado aqui),
--duration (minutos como um número inteiro, ou REMINDER; NONE não é aceito neste
endpoint), --starting-on, --ideal-time (HH:mm), --schedule (padrão Work Hours),
--description, --priority (apenas HIGH/MEDIUM).
--starting-on é enviado para a API exatamente como fornecido — sem cortes.
--frequency é uma string no próprio DSL do Motion, não um objeto. Isso pega as pessoas
de surpresa. O vocabulário está documentado em https://docs.usemotion.com/cookbooks/frequency/:
- Códigos de dia:
MO,TU,WE,TH,FR,SA,SU - Diário:
daily_every_day,daily_every_week_day,daily_specific_days_[MO, TU, FR] - Semanal:
weekly_any_day,weekly_any_week_day,weekly_specific_days_[MO, FR] - Quinzenal:
biweekly_first_week_any_day,biweekly_first_week_specific_days_[MO, TU],biweekly_second_week_any_week_day - Mensal por dia da semana:
monthly_first_MO,monthly_second_TU,monthly_last_FR - Mensal por data:
monthly_1,monthly_15,monthly_31(um dia fora do intervalo cai no último dia do mês) - Mensal por semana:
monthly_any_day_first_week,monthly_any_week_day_last_week,monthly_last_day_of_month - Trimestral:
quarterly_first_day,quarterly_last_week_day,quarterly_any_day_first_week
Um array de dias deve sempre ser pareado com um tipo de frequência — [MO, FR] sozinho não é válido.
Uma chamada completa, já que a string de frequência é a parte difícil:
python3 motion.py recurring-tasks create \
--name "Revisão semanal" --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
Observe os conjuntos mais restritos aqui: tarefas recorrentes aceitam apenas prioridade HIGH/MEDIUM e apenas
tipos de prazo HARD/SOFT, enquanto o resto da API também permite ASAP/LOW e NONE.
A CLI impõe ambos.
Comentários
| Comando | Endpoint |
|---|---|
comments list --task-id … | GET /v1/comments |
comments create --task-id … [--content …] | POST /v1/comments |
--content é Github Flavored Markdown na entrada e retorna como HTML. É
opcional — sem --content o corpo da requisição carrega apenas o id da tarefa. Se o Motion
aceita um comentário vazio não é verificado; passe --content quando você quiser dizer algo.
Campos personalizados — observe o prefixo /beta/
| Comando | Endpoint |
|---|---|
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} |
Os 12 tipos de campo personalizado, exatamente como escritos: text, url, date, person,
multiPerson, phone, select, multiSelect, number, email, checkbox, relatedTo.
Definir um valor precisa de --custom-field-instance-id (o campo no espaço de trabalho), --type,
e --value. --value é JSON, então cite-o corretamente: --value '"olá"' para uma string,
--value '42' para um número, --value '["A","B"]' para uma seleção múltipla, --value 'true' para
uma caixa de seleção.
python3 motion.py custom-field-values add-task tsk_123 \
--custom-field-instance-id cfi_1 --type select --value '"Alfa"'
custom-fields create aceita --metadata como JSON — format (plain/formatted/percent,
apenas números), toggle para caixas de seleção e options para campos de seleção:
--metadata '{"options":[{"id":"a","value":"Alfa","color":"#ff0000"}]}'
Usuários, espaços de trabalho, status, agendas
| Comando | Endpoint |
|---|---|
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 é repetível, e --all segue o cursor.
users list deve ser escopado. Passe --workspace-id ou --team-id; o script recusa uma
chamada sem escopo antecipadamente com essa mensagem em vez de gastar uma requisição no 400 que a API
retorna. Para listar todos, itere sobre seus espaços de trabalho.
schedules list retorna janelas de horário comercial, não eventos. Cada agenda tem seu próprio
timezone e um array por dia de blocos {start, end} — e um dia pode conter múltiplos
blocos (por exemplo, uma agenda de transporte escolar com um bloco matinal e outro noturno). Não leia isso como
"o usuário tem um bloco de trabalho por dia", e não trate como o calendário; para o que
está realmente agendado, leia os valores scheduledStart das tarefas.
Rota de escape 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":"oi"}'
python3 motion.py raw DELETE /v1/tasks/t1 --yes
Use isso para qualquer coisa que os comandos nomeados não cobrem. --query CHAVE=VALOR é repetível e repetir a
…
[truncado para tradução]