Este projeto apresenta uma solução completa e escalável para integração de dados entre ClickUp (gestão de tarefas), Frame.io (revisão de vídeos) e Power BI (Business Intelligence).
Com o uso do Power Query (M Language) para consumir diretamente as APIs REST dessas plataformas, o Studio Tracker automatiza a coleta, transformação e consolidação de informações — eliminando processos manuais, planilhas complexas ou infraestruturas de ETL e scripts fragmentados.
⚠️ Observação: O uso das APIs das plataformas neste projeto serve apenas como exemplo prático de integração via Power Query. O foco é demonstrar técnicas de consumo de APIs paginadas e a consolidação de múltiplas fontes em um único dashboard de BI.
No exemplo só vou me ater a utulizar a API do ClickUp já que o método é identico o que muda só é a variáveis de negócio de cada API
├─ 📁 assets
│ └─ 📁 assignee # Avatar dos usuários
│
└─ 📁 env # Credenciais
├─ 📄 clickup_token.txt
└─ 📄 frameio_token.txt
│
├─ 📁 power_bi_file # Arquivo .pbix
└─ 📊 Animation Studio.pbix
| Recurso | Descrição |
|---|---|
| Dashboard Interativo | Acesse o resultado final da integração no Power BI: Dashboard |
| Artigo Detalhado | Saiba mais sobre o contexto do projeto e sua trajetória: Artigo |
Baixe ou clone este repositório para sua máquina local:
git clone https://github.com/mmnitzsche/powerquery_api_integrationA pasta env armazena os tokens de autenticação necessários. Crie dois arquivos de texto exatamente com os nomes abaixo dentro da pasta env, e cole seu token API em cada um:
| Arquivo | Conteúdo | Documentação API |
|---|---|---|
env/clickup_token.txt |
Seu Personal API Token do ClickUp | Documentação ClickUp API |
env/frameio_token.txt |
Seu Developer Token do Frame.io | Documentação Frame.io API |
Importante: Esses arquivos estão listados no
.gitignorepara garantir que suas credenciais não sejam expostas publicamente.
Esta seção demonstra como o Power Query é usado para consumir e transformar dados do ClickUp, oferecendo uma alternativa barata dentro do possível da solução desejada.
Embora scripts em Python ofereçam maior flexibilidade e possibilidades avançadas de manipulação de dados, exigem bibliotecas externas (requests, pandas) e um ambiente de execução dedicado.
O Power Query, por outro lado, permite conectar-se diretamente a APIs REST, transformar e consolidar dados de forma visual e automatizada, servindo como uma alternativa prática e escalável que pode substituir parte da infraestrutura mais complexa necessária em soluções baseadas apenas em código.
- Conexão Nativa: Conecta-se diretamente a APIs REST com autenticação e cabeçalhos integrados.
- Transformação Visual: Expande e renomeia campos JSON de forma intuitiva.
- Automação Completa: Atualiza dados automaticamente no Power BI sem necessidade de código adicional.
Abaixo vamos começar a entrar no passo a passo e ver como funciona requisições do tipo GET no Power Query.
clickup_token =
let
Source = Table.FromColumns({Lines.FromBinary(File.Contents("C:\Users\Mateus\Documents\Blog\Animation Studio\env\clickup_token.txt"), null, null, 1252)}),
#"Renamed Columns" = Table.RenameColumns(Source,{{"Column1", "token"}})
in
#"Renamed Columns",
// Cabeçalho da requisição
headers = [
#"accept" = "application/json",
#"Authorization" = clickup_token{0}[token]
]
Equivalente em Python:
import requests
token = "SEU_TOKEN"
headers = {"Authorization": token}
response = requests.get("https://api.clickup.com/api/v2/team", headers=headers)
data = response.json()A seguir, funções que consultam diferentes endpoints para obter teams, spaces e lists — necessárias antes de buscar as tasks:
// Função para obter os teams
getTeams = (team_name as text) as record =>
let
response = Json.Document(Web.Contents("https://api.clickup.com", [RelativePath="api/v2/team", Headers=headers])),
teams = response[teams],
dfTeams = Table.FromRecords(teams),
team_id = Record.Field(List.First(List.Select(teams, each _[name] = team_name)), "id")
in
[df=dfTeams, id=team_id],
// Função para obter os spaces
getSpaces = (team_id as text, space_name as text) as record =>
let
response = Json.Document(Web.Contents("https://api.clickup.com", [RelativePath="api/v2/team/" & team_id & "/space", Headers=headers])),
spaces = response[spaces],
dfSpaces = Table.FromRecords(spaces),
space_id = Record.Field(List.First(List.Select(spaces, each _[name] = space_name)), "id")
in
[df=dfSpaces, id=space_id],
// Função para obter as listas
getLists = (space_id as text) as record =>
let
response = Json.Document(Web.Contents("https://api.clickup.com", [RelativePath="api/v2/space/" & space_id & "/list", Headers=headers])),
lists = response[lists],
dfLists = Table.FromRecords(lists),
list_id = Record.Field(List.First(List.Select(lists, each _[name] = "Scenes")), "id")
in
[df=dfLists, id=list_id]
O endpoint /list é o mais importante, pois fornece a base de dados principal usada no dashboard.
getAllTasks = (list_id as text) as table =>
let
GetTasksPerPage = (page as number) as list =>
let
response = Json.Document(Web.Contents("https://api.clickup.com", [RelativePath="api/v2/list/" & list_id & "/task", Query=[page=Number.ToText(page), include_closed="true"], Headers=headers])),
tasks = try response[tasks] otherwise {}
in
tasks,
FetchAllTasks = List.Generate(
() => [page = 0, tasks = GetTasksPerPage(0)],
each List.Count([tasks]) > 0,
each [page = [page] + 1, tasks = GetTasksPerPage([page])],
each [tasks]
),
allTasks = List.Combine(FetchAllTasks),
dfTasks = Table.FromRecords(allTasks)
in
dfTasks
Exemplo de resposta JSON:
{
"id": "86a6x2gxf",
"name": "NITIT_004_009",
"Status": "Open",
"status.id": "sc901307541828_fdONRjp8",
"status.color": "#87909e",
"creator.username": "Mateus Nitzsche",
"creator.email": "mmnitzsche@gmail.com",
"date_created": 1740374812732,
"date_updated": 1740599141056,
"index": 1,
"url": "https://app.clickup.com/t/86a6x2gxf"
}| id | name | Status | status.id | status.color | creator.username | creator.email | date_created | date_updated | index | url |
|---|---|---|---|---|---|---|---|---|---|---|
| 86a6x2gxf | NITIT_004_009 | Open | sc901307541828_fdONRjp8 | #87909e | Mateus Nitzsche | mmnitzsche@gmail.com | 1740374812732 | 1740599141056 | 1 | https://app.clickup.com/t/86a6x2gxf |
- Requisição por página (
GetTasksPerPage) → Busca tasks por página e retorna lista vazia caso não haja resultados. - Iteração declarativa (
List.Generate) → Cria sequência de páginas enquanto houver tasks, sem loops manuais. - Combinação de resultados (
List.Combine) → Consolida as listas de páginas em uma única lista completa. - Transformação final (
Table.FromRecords) → Converte JSON em tabela, permitindo expandir colunas, renomear campos e adicionar índices.
Em Python, o mesmo processo exigiria um loop
whilecomrequests.get(). No Power Query, tudo é declarativo — definido por estado inicial, condição de continuação e valor retornado.
Após coletar os dados das duas fontes (ClickUp e Frame.io), é essencial criar uma camada de negócios para identificar os pontos em comum entre elas.
No contexto deste projeto, os dois campos que serviram de chave de integração foram o nome da tarefa e o nome do arquivo de vídeo, que possuem valores equivalentes.
Foi necessário apenas um pequeno tratamento nos dados do Frame.io, já que os nomes dos arquivos incluem a extensão .mp4 (ex.: NITIT_004_009.mp4), permitindo a consolidação correta das informações.
'
| Recurso | Descrição |
|---|---|
| Portifólio | Confira mais conteúdos sobre dados, BI e projetos : bit.ly/mateusnit |
| Dashboard Interativo | Acesse o resultado final da integração no Power BI: Dashboard |
| Artigo Detalhado | Saiba mais sobre o contexto do projeto e sua trajetória: Artigo |


