O SDK dos scripts
Os oito módulos do SDK api_manager — datasources, HTTP, logs, ficheiros de upload, secrets, notificações, workflows e relatórios — com exemplos Python.
Dentro de um script, a app inteira está ao alcance de um import. O SDK
chama-se api_manager e importa-se módulo a módulo:
from api_manager import db, http, log, files, secrets, notify, workflow, reports
Estes oito nomes são a superfície pública inteira do SDK — o que não está aqui não existe em execução:
| Módulo | Para quê |
|---|---|
db |
Consultar os datasources da app (Oracle, PostgreSQL, MySQL/MariaDB, SQL Server, …). |
http |
Pedidos HTTP a serviços externos, com JSON automático. |
log |
Escrever linhas nos logs da execução. |
files |
Ler ficheiros enviados por upload numa API. |
secrets |
Ler segredos da app, decifrados no servidor. |
notify |
Notificações in-app e emails aos utilizadores da app. |
workflow |
Arrancar processos, acordar esperas e decidir tarefas humanas. |
reports |
Gerar os relatórios da app em PDF ou Excel. |
O editor autocompleta tudo isto — os nomes, os parâmetros e a documentação de cada função aparecem enquanto escreves.
Dica
O botão Prompt para LLM na barra do editor abre o guia completo do SDK, pronto a copiar com Copiar tudo. Cola-o no Claude, ChatGPT ou outro assistente, junto com o que queres que o script faça — o assistente passa a conhecer o contrato exacto e não inventa funções que não existem.

db — os datasources da app
Os datasources configurados na app referenciam-se pelo nome. Duas funções:
from api_manager import db
crm = db("Dados CRM")
linhas = crm.query( # lista de dicts (coluna -> valor)
"select id, nome from contas where cidade = ?", ["Lisboa"]
)
conta = crm.query_one( # a primeira linha, ou None
"select * from contas where id = ?", [42]
)
params é uma lista posicional. Os placeholders são os do motor do
datasource:
| Motor | Placeholders | Exemplo |
|---|---|---|
| PostgreSQL | $1, $2, … |
where id = $1 |
| MySQL / MariaDB | ? |
where id = ? |
| Oracle | :1, :2, … |
where id = :1 |
Se o datasource não existir, a chamada lança um erro (e a execução termina em Erro, se não o apanhares).
Atenção
Datas em Oracle: evita passá-las como parâmetro — o transporte em JSON
torna o tipo ambíguo. Prefere o literal no SQL:
TO_DATE('2026-06-25','YYYY-MM-DD').
http — pedidos a serviços externos
Cinco verbos, todos com JSON automático — um body dict ou lista segue como
JSON, e uma resposta JSON chega já descodificada:
from api_manager import http, log
r = http.get("https://api.exemplo.com/clientes")
# r = {"status": int, "ok": bool, "body": <json descodificado ou texto>}
if r["ok"]:
log("recebidos", len(r["body"]), "clientes")
r = http.post(
"https://api.exemplo.com/leads",
body={"nome": "Vininha & Filhos", "origem": "keplin"},
headers={"Authorization": "Bearer abc123"},
)
Também existem http.put(url, body, headers), http.patch(url, body, headers) e http.delete(url, headers). r["ok"] é verdadeiro para
respostas 2xx — um 404 ou 500 não lança excepção, verifica tu o estado.
log — os logs da execução
from api_manager import log
log("a processar", 42, {"fase": "inicial"})
Aceita vários argumentos, como o print — e cada chamada é uma linha nos
logs da execução, visível no painel Resultado da execução e no
histórico Execuções. O print clássico também é capturado, mas log é
a forma canónica do SDK.

files — ficheiros enviados por upload
Quando uma API da app tem um argumento do tipo Upload, o cliente envia
um ficheiro e o script recebe-o em input["args"] como um handle — os
bytes ficam em disco, não na memória. O módulo files opera sobre esse
handle:
from api_manager import files
def main(input):
f = input["args"]["ficheiro"] # o handle do upload
texto = files.read_text(f) # str (utf-8)
dados = files.read(f) # bytes
caminho = files.path(f) # caminho absoluto em disco
files.save(f, "ultimo-recebido.csv") # copia para a pasta do script
return {"nome": f["filename"], "tamanho": f["size"]}
O handle traz filename, mimeType e size — úteis para validar antes de
processar.
secrets — segredos da app
Tokens e chaves guardam-se na página Secrets da app (árvore lateral, grupo App), cifrados. O script lê-os pela chave:
from api_manager import secrets
token = secrets.get("STRIPE_KEY") # -> str | None
if token is None:
raise RuntimeError("Secret STRIPE_KEY não configurado nesta app.")
O valor é decifrado no servidor, no momento da execução — nunca aparece no browser nem fica no código.

Atenção
Não faças log(token). Os logs ficam no histórico de execuções — um
segredo escrito num log deixou de ser segredo.
notify — notificações e emails
A app tem dois canais fixos — um in-app e um de email — activáveis
nas definições de Notificações da app (é lá que vive a configuração
SMTP e o remetente). users são usernames de utilizadores da app;
roles expandem para todos os membros do role.
In-app, em tempo real:
from api_manager import notify
r = notify.send(
"Relatório pronto",
subtitle="Relatórios mensais",
body="O relatório mensal está disponível.",
users=["joao"],
roles=None,
data={"url": "/relatorios/42"},
)
# r = {"recipients": int, "delivered": int}
delivered conta os entregues em tempo real, a quem está ligado; os
restantes ficam no inbox e recebem ao entrar.
Email:
r = notify.email(
"Alerta de stock",
to=["chefe@empresa.pt"], # endereços livres
users=None,
roles=["admin"], # e/ou utilizadores e roles da app
text="Stock abaixo do mínimo.",
html=None,
)
# r = {"accepted": [emails], "skipped": [usernames sem email]}
O envio SMTP acontece de forma assíncrona no servidor — o script não fica à
espera. attachments aceita anexos com o conteúdo em base64 (ver reports
abaixo para o caso típico).
workflow — os processos da app
Um script pode arrancar um processo, acordar quem está à espera de um
evento, e decidir tarefas humanas. O processo referencia-se pelo nome (ou
pelo identificador estável); key é a chave do registo sobre o qual ele
corre:
from api_manager import workflow
r = workflow.start("Aprovação de despesa", 42, data={"valor": 1200})
# r = {"instanceId": int}
r = workflow.signal("visto", key=42)
# r = {"woken": int}
abertas = workflow.tasks("joao")
# tarefas abertas desse utilizador da app, com as decisões possíveis
r = workflow.complete(task=17, outcome="aprovar", as_user="joao",
data={"nota": "ok"})
# r = {"ok": bool}
Regras que importam:
workflow.startdevolve assim que o motor chega à primeira espera — nunca fica à espera de que o processo acabe.workflow.signalsemkeyacorda todos os processos parados nesse evento;{"woken": 0}não é erro — pode não haver ninguém à espera.- Em
workflow.complete,as_useré obrigatório (quem decide fica no histórico) e a tarefa tem de estar atribuída a esse utilizador. Os valores deoutcomesão as saídas do nó da tarefa — os mesmos que a pessoa vê como botões. Não inventes nomes.
reports — gerar relatórios da app
O documento é gerado no servidor, com os dados da app, a partir de um relatório existente:
from api_manager import notify, reports
r = reports.render("Facturas do mês", {"mes": "2026-03"})
# r = {"filename", "pages", "format", "mime", "bytes", "base64"}
notify.email(
"Facturas de Março",
to=["financeiro@empresa.pt"],
text="Segue em anexo.",
attachments=[{"filename": r["filename"], "content": r["base64"]}],
)
O terceiro argumento opcional é o formato: "pdf" (omissão) ou "xlsx".
Os parâmetros são os que o relatório declara. No resultado, bytes vem
pronto a gravar em disco e base64 pronto a anexar num email.
Um exemplo completo
Do género do script atualizar_indicadores da app Gestão de Clientes —
lê o pipeline, escreve logs úteis e avisa quando há fechos próximos:
from datetime import date, timedelta
from api_manager import db, log, notify
def main(input):
crm = db("Dados CRM")
abertas = crm.query_one(
"select count(*) as n, coalesce(sum(valor), 0) as total "
"from oportunidades where fase not in ('fechada_ganha', 'fechada_perdida')"
)
limite = (date.today() + timedelta(days=7)).isoformat()
fechos = crm.query(
"select titulo, data_fecho from oportunidades "
"where fase not in ('fechada_ganha', 'fechada_perdida') "
"and data_fecho <= ? order by data_fecho",
[limite],
)
log("pipeline:", abertas["n"], "oportunidades /", abertas["total"], "EUR")
if fechos:
notify.send(
"Fechos esta semana",
body=f"{len(fechos)} oportunidades com fecho previsto até {limite}.",
roles=["comercial"],
)
return {
"oportunidades_abertas": abertas["n"],
"valor_pipeline": abertas["total"],
"fechos_proximos_7_dias": len(fechos),
}
Limites e boas práticas
- O resultado de
maintem de ser serializável em JSON; máximo 32 MB. - Cada execução respeita o Tempo limite do script (30 segundos a 10 minutos) — ao excedê-lo, o processo é terminado e fica Timeout.
- As execuções são isoladas: não dependas de variáveis de uma execução anterior. Persiste em ficheiros da pasta do script ou num datasource.
- Faz
log(...)em cada fase relevante — é o que vais ler quando algo falhar às 7h00 de um domingo. - Apanha os erros recuperáveis e regista-os; deixa subir os fatais — uma excepção não apanhada marca a execução como Erro, e é isso que queres ver no histórico quando algo está realmente mal.
- Não imprimas segredos, e lê-os sempre de
secrets— nunca os escrevas no código.
Perguntas frequentes
from api_manager import db falha no meu computador.
É esperado: o módulo api_manager só existe em execução na plataforma —
é lá que ele é injectado. No editor tens o autocompletar completo do SDK; o
ficheiro api_manager.py que vês na pasta do script existe só para isso.
db("nome") diz que o datasource não existe.
O nome tem de ser exactamente o do datasource na app (ex.: Dados CRM, com maiúsculas e espaço). Vê a lista na secção Datasources da
árvore.
O notify.email devolveu accepted mas o email não chegou.
O envio SMTP é assíncrono — accepted significa que o email seguiu para o
canal de email da app. Confirma a configuração SMTP nas definições de
Notificações da app e a caixa de spam do destinatário.
Posso usar o SDK na validação de uma janela?
Sim — a validação corre no mesmo ambiente do script principal, com o mesmo
input e o mesmo SDK. Ver Agendamentos.