KEPLIN Docs

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.

O Prompt para LLM — todo o contrato do SDK num texto pronto a colar num assistente de IA.
O Prompt para LLM — todo o contrato do SDK num texto pronto a colar num assistente de IA.

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.

O detalhe de uma execução — o resultado devolvido e as linhas de log escritas pelo script.
O detalhe de uma execução — o resultado devolvido e as linhas de log escritas pelo script.

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.

A página Secrets da app — as chaves que os scripts lêem com secrets.get.
A página Secrets da app — as chaves que os scripts lêem com secrets.get.

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.start devolve assim que o motor chega à primeira espera — nunca fica à espera de que o processo acabe.
  • workflow.signal sem key acorda 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 de outcome sã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 main tem 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.