KEPLIN Docs

El SDK de los scripts

Los ocho módulos del SDK api_manager — datasources, HTTP, logs, archivos de subida, secrets, notificaciones, workflows e informes — con ejemplos Python.

Dentro de un script, la app entera está al alcance de un import. El SDK se llama api_manager y se importa módulo a módulo:

from api_manager import db, http, log, files, secrets, notify, workflow, reports

Estos ocho nombres son la superficie pública entera del SDK — lo que no está aquí no existe en ejecución:

Módulo Para qué
db Consultar los datasources de la app (Oracle, PostgreSQL, MySQL/MariaDB, SQL Server, …).
http Peticiones HTTP a servicios externos, con JSON automático.
log Escribir líneas en los logs de la ejecución.
files Leer archivos enviados por subida en una API.
secrets Leer secretos de la app, descifrados en el servidor.
notify Notificaciones in-app y correos a los usuarios de la app.
workflow Arrancar procesos, despertar esperas y decidir tareas humanas.
reports Generar los informes de la app en PDF o Excel.

El editor autocompleta todo esto — los nombres, los parámetros y la documentación de cada función aparecen mientras escribes.

Consejo

El botón Prompt para LLM en la barra del editor abre la guía completa del SDK, lista para copiar con Copiar todo. Pégala en Claude, ChatGPT u otro asistente, junto con lo que quieres que el script haga — el asistente pasa a conocer el contrato exacto y no inventa funciones que no existen.

El Prompt para LLM — todo el contrato del SDK en un texto listo para pegar en un asistente de IA.
El Prompt para LLM — todo el contrato del SDK en un texto listo para pegar en un asistente de IA.

db — los datasources de la app

Los datasources configurados en la app se referencian por el nombre. Dos funciones:

from api_manager import db

crm = db("Dados CRM")

linhas = crm.query(          # lista de dicts (columna -> valor)
    "select id, nome from contas where cidade = ?", ["Lisboa"]
)
conta = crm.query_one(       # la primera fila, o None
    "select * from contas where id = ?", [42]
)

params es una lista posicional. Los placeholders son los del motor del datasource:

Motor Placeholders Ejemplo
PostgreSQL $1, $2, … where id = $1
MySQL / MariaDB ? where id = ?
Oracle :1, :2, … where id = :1

Si el datasource no existe, la llamada lanza un error (y la ejecución termina en Error, si no lo capturas).

Atención

Fechas en Oracle: evita pasarlas como parámetro — el transporte en JSON vuelve el tipo ambiguo. Prefiere el literal en el SQL: TO_DATE('2026-06-25','YYYY-MM-DD').

Una columna binaria (bytea, blob, varbinary) llega como bytes, y un parámetro en bytes se graba como binario:

foto = crm.query_one("select foto from contas where id = ?", [42])["foto"]
crm.query("update contas set foto = ? where id = ?", [novos_bytes, 42])

http — peticiones a servicios externos

Cinco verbos, todos con JSON automático — un body dict o lista va como JSON, y una respuesta JSON llega ya descodificada:

from api_manager import http, log

r = http.get("https://api.ejemplo.com/clientes")
# r = {"status": int, "ok": bool, "body": <json descodificado o texto>}
if r["ok"]:
    log("recibidos", len(r["body"]), "clientes")

r = http.post(
    "https://api.ejemplo.com/leads",
    body={"nome": "Vininha & Filhos", "origem": "keplin"},
    headers={"Authorization": "Bearer abc123"},
)

También existen http.put(url, body, headers), http.patch(url, body, headers) y http.delete(url, headers). r["ok"] es verdadero para respuestas 2xx — un 404 o 500 no lanza excepción, verifica tú el estado.

log — los logs de la ejecución

from api_manager import log

log("procesando", 42, {"fase": "inicial"})

Acepta varios argumentos, como el print — y cada llamada es una línea en los logs de la ejecución, visible en el panel Resultado de la ejecución y en el historial Ejecuciones. El print clásico también se captura, pero log es la forma canónica del SDK.

El detalle de una ejecución — el resultado devuelto y las líneas de log escritas por el script.
El detalle de una ejecución — el resultado devuelto y las líneas de log escritas por el script.

files — archivos enviados por subida

Cuando una API de la app tiene un argumento de tipo Upload, el cliente envía un archivo y el script lo recibe en input["args"] como un handle — los bytes quedan en disco, no en la memoria. El módulo files opera sobre ese handle:

from api_manager import files


def main(input):
    f = input["args"]["ficheiro"]      # el handle de la subida
    texto = files.read_text(f)         # str (utf-8)
    dados = files.read(f)              # bytes
    caminho = files.path(f)            # ruta absoluta en disco
    files.save(f, "ultimo-recebido.csv")   # copia a la carpeta del script
    return {"nome": f["filename"], "tamanho": f["size"]}

El handle trae filename, mimeType y size — útiles para validar antes de procesar.

secrets — secretos de la app

Los tokens y claves se guardan en la página Secrets de la app (árbol lateral, grupo App), cifrados. El script los lee por la clave:

from api_manager import secrets

token = secrets.get("STRIPE_KEY")   # -> str | None
if token is None:
    raise RuntimeError("Secret STRIPE_KEY no configurado en esta app.")

El valor se descifra en el servidor, en el momento de la ejecución — nunca aparece en el navegador ni queda en el código.

La página Secrets de la app — las claves que los scripts leen con secrets.get.
La página Secrets de la app — las claves que los scripts leen con secrets.get.

Atención

No hagas log(token). Los logs quedan en el historial de ejecuciones — un secreto escrito en un log dejó de ser secreto.

notify — notificaciones y correos

La app tiene dos canales fijos — uno in-app y uno de correo — activables en los ajustes de Notificaciones de la app (ahí vive la configuración SMTP y el remitente). users son usernames de usuarios de la app; roles se expanden a todos los miembros del role.

In-app, en tiempo real:

from api_manager import notify

r = notify.send(
    "Informe listo",
    subtitle="Informes mensuales",
    body="El informe mensual está disponible.",
    users=["joao"],
    roles=None,
    data={"url": "/relatorios/42"},
)
# r = {"recipients": int, "delivered": int}

delivered cuenta los entregados en tiempo real, a quien está conectado; los demás quedan en el buzón y lo reciben al entrar.

Correo:

r = notify.email(
    "Alerta de stock",
    to=["jefe@empresa.es"],       # direcciones libres
    users=None,
    roles=["admin"],              # y/o usuarios y roles de la app
    text="Stock por debajo del mínimo.",
    html=None,
)
# r = {"accepted": [emails], "skipped": [usernames sin correo]}

El envío SMTP ocurre de forma asíncrona en el servidor — el script no se queda esperando. attachments acepta adjuntos con el contenido en base64 (ver reports abajo para el caso típico).

workflow — los procesos de la app

Un script puede arrancar un proceso, despertar a quien está esperando un evento, y decidir tareas humanas. El proceso se referencia por el nombre (o por el identificador estable); key es la clave del registro sobre el que corre:

from api_manager import workflow

r = workflow.start("Aprobación de gasto", 42, data={"valor": 1200})
# r = {"instanceId": int}

r = workflow.signal("visto", key=42)
# r = {"woken": int}

abertas = workflow.tasks("joao")
# tareas abiertas de ese usuario de la app, con las decisiones posibles

r = workflow.complete(task=17, outcome="aprovar", as_user="joao",
                      data={"nota": "ok"})
# r = {"ok": bool}

Reglas que importan:

  • workflow.start devuelve en cuanto el motor llega a la primera espera — nunca se queda esperando a que el proceso acabe.
  • workflow.signal sin key despierta a todos los procesos parados en ese evento; {"woken": 0} no es error — puede no haber nadie esperando.
  • En workflow.complete, as_user es obligatorio (quien decide queda en el historial) y la tarea tiene que estar asignada a ese usuario. Los valores de outcome son las salidas del nodo de la tarea — los mismos que la persona ve como botones. No inventes nombres.

reports — generar informes de la app

El documento se genera en el servidor, con los datos de la app, a partir de un informe existente:

from api_manager import notify, reports

r = reports.render("Facturas del mes", {"mes": "2026-03"})
# r = {"filename", "pages", "format", "mime", "bytes", "base64"}

notify.email(
    "Facturas de marzo",
    to=["financiero@empresa.es"],
    text="Va en adjunto.",
    attachments=[{"filename": r["filename"], "content": r["base64"]}],
)

El tercer argumento opcional es el formato: "pdf" (por defecto) o "xlsx". Los parámetros son los que el informe declara. En el resultado, bytes viene listo para grabar en disco y base64 listo para adjuntar en un correo.

Un script se ejecuta sin nadie con sesión: reports.render y los verbos de workflow leen y escriben los datos como la propia app, con todas las API activas, tanto en una programación como en Ejecutar ahora.

Un ejemplo completo

Del estilo del script atualizar_indicadores de la app Gestão de Clientes — lee el pipeline, escribe logs útiles y avisa cuando hay cierres 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(
            "Cierres esta semana",
            body=f"{len(fechos)} oportunidades con cierre previsto hasta {limite}.",
            roles=["comercial"],
        )

    return {
        "oportunidades_abertas": abertas["n"],
        "valor_pipeline": abertas["total"],
        "fechos_proximos_7_dias": len(fechos),
    }

Límites y buenas prácticas

  • El resultado de main tiene que ser serializable en JSON; máximo 32 MB.
  • Cada ejecución respeta el Tiempo límite del script (30 segundos a 10 minutos) — al excederlo, el proceso se termina y queda Timeout.
  • Las ejecuciones son aisladas: no dependas de variables de una ejecución anterior. Persiste en archivos de la carpeta del script o en un datasource.
  • Haz log(...) en cada fase relevante — es lo que vas a leer cuando algo falle a las 7h00 de un domingo.
  • Captura los errores recuperables y regístralos; deja subir los fatales — una excepción no capturada marca la ejecución como Error, y eso es lo que quieres ver en el historial cuando algo está realmente mal.
  • No imprimas secretos, y léelos siempre de secrets — nunca los escribas en el código.

Preguntas frecuentes

from api_manager import db falla en mi ordenador. Es lo esperado: el módulo api_manager solo existe en ejecución en la plataforma — ahí es donde se inyecta. En el editor tienes el autocompletado completo del SDK; el archivo api_manager.py que ves en la carpeta del script existe solo para eso.

db("nombre") dice que el datasource no existe. El nombre tiene que ser exactamente el del datasource en la app (ej.: Dados CRM, con mayúsculas y espacio). Mira la lista en la sección Datasources del árbol.

El notify.email devolvió accepted pero el correo no llegó. El envío SMTP es asíncrono — accepted significa que el correo siguió hacia el canal de correo de la app. Confirma la configuración SMTP en los ajustes de Notificaciones de la app y la carpeta de spam del destinatario.

¿Puedo usar el SDK en la validación de una ventana? Sí — la validación corre en el mismo entorno del script principal, con el mismo input y el mismo SDK. Ver Programaciones.