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.

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.

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.

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.startdevuelve en cuanto el motor llega a la primera espera — nunca se queda esperando a que el proceso acabe.workflow.signalsinkeydespierta a todos los procesos parados en ese evento;{"woken": 0}no es error — puede no haber nadie esperando.- En
workflow.complete,as_useres obligatorio (quien decide queda en el historial) y la tarea tiene que estar asignada a ese usuario. Los valores deoutcomeson 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
maintiene 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.