TextOnFlow
API v6.0 openapi.json Volver al editor

Documentación técnica

TextOnFlow es una API de renderizado de imágenes personalizadas, diseñada para agencias ManyChat que gestionan campañas de marketing por WhatsApp Business. Cada solicitud produce una imagen PNG con texto dinámico, overlays y efectos visuales en milisegundos.

API Online Python · FastAPI · Pillow Google Gemini 2.5 Flash (IA)

Inicio rápido

Una sola llamada POST a /generate-multi recibe una imagen base + texto dinámico y devuelve la URL de la imagen renderizada lista para enviar por WhatsApp.

# Ejemplo mínimo — cURL
curl -X POST https://www.textonflow.com/generate-multi \
  -H "Content-Type: application/json" \
  -d '{
    "template_name": "mi-plantilla.jpg",
    "texts": [
      {
        "text": "Hola, {nombre}",
        "x": 100, "y": 80,
        "font_size": 72,
        "font_color": "#FFFFFF",
        "font_name": "DejaVuSans-Bold"
      }
    ],
    "vars": { "nombre": "Ana García" }
  }'
Respuesta: JSON con {"url": "https://www.textonflow.com/image/abc123.png"}. Esa URL es la imagen final lista para usar en el nodo "Send Image" de ManyChat.

🌐 Base URL

Base URL https://www.textonflow.com

Todos los endpoints son HTTPS. No se requiere API key para /generate-multi. El servidor está alojado en Railway (región US-West) con dominio personalizado via SiteGround.

CORS: Todos los orígenes están permitidos (*). Puedes llamar la API directamente desde ManyChat HTTP Request, Make.com, n8n, Zapier o cualquier cliente HTTP.

🖼️ Renderizar imagen

POST /generate-multi Endpoint principal de renderizado

Recibe una imagen base (plantilla) y un array de campos de texto, y devuelve una imagen PNG compuesta con todos los elementos renderizados.

Body (JSON)

CampoTipoReq.DefectoDescripción
template_namestringNombre del archivo en /templates/ (ej. oferta.jpg) o URL completa HTTPS de la imagen base
textsTextField[]Array de campos de texto a renderizar (ver modelo TextField)
varsobject{}Variables de sustitución. Reemplaza {clave} en cualquier text del array
overlaysImageOverlay[][]Imágenes a superponer (foto de perfil del cliente, logo, etc.)
shapesCanvasShape[][]Formas geométricas (rectángulos, círculos, estrellas)
filter_namestring"none"Filtro de color aplicado a la imagen base antes de renderizar texto. Ver lista de filtros.
render_scaleint11 = rápido (recomendado para ManyChat), 2 = alta calidad (editor)
watermarkboolfalseSi true, agrega el sello textonflow.com en la esquina inferior derecha

Respuesta exitosa (200)

{
  "url": "https://www.textonflow.com/image/a3f8c2d1.png",
  "width": 1080,
  "height": 1080,
  "texts_rendered": 2
}
Vida útil de la imagen: Las imágenes generadas se almacenan temporalmente en el servidor. Úsalas inmediatamente en tu flujo de ManyChat — no las uses como CDN permanente.
GET /image/{filename} Recuperar imagen renderizada

Devuelve el archivo PNG generado por /generate-multi. La URL completa viene en el campo url de la respuesta de renderizado — úsala directamente en el nodo de imagen de ManyChat.

Generación de imágenes con IA

Genera imágenes con Google Gemini 2.5 Flash Image. El proceso es asíncrono: primero inicias el job y luego haces polling hasta recibir el resultado. El texto del prompt pasa por el modelo Gemini 2.0 Flash para enriquecimiento antes de generarse.

POST /api/generate-image Iniciar generación IA (async)

Body (JSON)

CampoTipoReq.Descripción
promptstringDescripción en texto de la imagen a generar
stylestringEstilo visual aplicado al prompt. Ver lista completa abajo.
aspect_ratiostring1:1, 9:16, 3:4, 4:3, 16:9, 4:5, 3:2, 2:3 — Defecto: 1:1
widthintegerAncho de salida en px (si se usa aspecto personalizado). Rango: 64–4096.
heightintegerAlto de salida en px (si se usa aspecto personalizado). Rango: 64–4096.
reference_imagesarrayHasta 5 imágenes de referencia. Cada objeto: {"mime_type": "image/jpeg", "data": "<base64>"}

Estilos visuales disponibles

ValorDescripción
cinematograficoCinematográfico — iluminación dramática, colores profundos
bocetoBoceto a lápiz sobre papel
acuarelaPintura en acuarela con bordes suaves
steampunkEstética victoriana con maquinaria de vapor
risografiaRisografía — colores planos, trama de puntos
monocromoBlanco y negro con alto contraste
pintura-oleoPintura al óleo con textura de pinceladas
dibujo-antiguoGrabado antiguo / ilustración vintage
cyborgCyberpunk / neón con elementos tecnológicos
plumillaPlumilla — tinta china con trazos nítidos
amanecerPaleta de amanecer: rosados, naranjas y azules
colorExplosión de color vibrante

Respuesta (200)

{ "job_id": "550e8400-e29b-41d4-a716-446655440000" }
GET /api/image-job/{job_id} Polling del resultado

Estados de respuesta (polling)

// Pendiente — seguir haciendo polling cada 2 segundos
{ "status": "pending" }

// Completado — imagen disponible
{
  "status": "done",
  "url": "https://www.textonflow.com/storage/gen_abc123.png"
}

// Error — el modelo rechazó el prompt o falló la generación
{ "status": "error", "error": "Prompt rejected by safety filters" }
Tiempo de generación: Entre 15 y 40 segundos dependiendo de la carga del modelo. Haz polling cada 2–3 segundos hasta recibir status: "done" o "error".

📱 Código QR

POST /api/qr Generar QR personalizado

Genera un QR como PNG en base64, listo para usarse como overlay en una imagen de plantilla.

Body (JSON)

CampoTipoReq.DefectoDescripción
textstringURL o texto a codificar en el QR
dark_colorstring"#000000"Color de los módulos del QR (hex)
light_colorstring"#FFFFFF"Color de fondo del QR (hex)
bg_colorstring"#FFFFFF"Color del área de padding (hex)
paddingint20Padding en píxeles alrededor del QR (0–120)

Respuesta (200)

{ "image": "data:image/png;base64,iVBORw0KGgo..." }

📦 Modelo — MultiTextRequest

El body completo del endpoint /generate-multi. Combina todos los elementos visuales en una sola solicitud atómica.

{
  "template_name": "plantilla-oferta.jpg",   // nombre en /templates/ o URL HTTPS
  "texts": [ /* array de TextField */ ],
  "vars": {                                     // variables de sustitución
    "nombre": "Ana García",
    "descuento": "30%"
  },
  "overlays": [ /* array de ImageOverlay */ ],
  "shapes": [ /* array de CanvasShape */ ],
  "filter_name": "none",
  "render_scale": 1,
  "watermark": false
}

✏️ Modelo — TextField

Define un bloque de texto con posición, tipografía, sombra, borde, fondo y efectos warp. Todos los campos son opcionales excepto text, x e y.

Posición y texto

CampoTipoDefectoDescripción
textstringrequeridoTexto a mostrar. Acepta {variables}, emojis y saltos de línea \n
xintrequeridoPosición X en píxeles desde la izquierda
yintrequeridoPosición Y en píxeles desde arriba
font_sizeint60Tamaño de fuente en puntos
font_colorstring"#FFFFFF"Color del texto en hex o rgb()
font_namestring"DejaVuSans-Bold"Nombre de la fuente (ver sección Fuentes)
rotationint0Rotación en grados (0–360)
alignmentstring"left"Anclaje del bloque: left, center, right
text_alignstring"center"Alineación interna del texto: left, center, right
line_spacingint10Espaciado extra entre líneas en píxeles

Text wrap automático

CampoTipoDefectoDescripción
text_wrap_enabledboolfalseActiva salto de línea automático por palabra
text_wrap_paddingint60Margen izquierdo+derecho en px. El texto ocupa ancho_imagen − 2×padding

Sombra del texto

CampoTipoDefectoDescripción
shadow_enabledboolfalseActiva la sombra
shadow_colorstring"#000000"Color de la sombra
shadow_opacityint100Opacidad 0–100
shadow_offset_xint2Desplazamiento horizontal en px
shadow_offset_yint2Desplazamiento vertical en px
shadow_blurint0Radio de desenfoque gaussiano (0 = sombra dura)
shadow_blend_modestring"normal"normal, multiply, darken, overlay, soft_light, screen

Contorno (stroke) del texto

CampoTipoDefectoDescripción
stroke_enabledboolfalseActiva el contorno del texto
stroke_colorstring"#000000"Color del contorno
stroke_opacityint100Opacidad 0–100
stroke_widthint2Grosor del contorno en píxeles

Caja de fondo del texto

CampoTipoDefectoDescripción
background_enabledboolfalseActiva la caja de fondo detrás del texto
background_colorstring"#000000"Color del fondo
background_opacityint80Opacidad 0–100
background_color_typestring"solid"solid, gradient2, instagram
background_gradient_color2string"#FFFFFF"Segundo color para tipo gradient2
background_gradient_angleint135Ángulo del gradiente en grados
background_radiusint0Radio de esquinas redondeadas en px
background_padding_top/right/bottom/leftint10Relleno interior de la caja (px por lado)
background_stroke_colorstring"#FFFFFF"Color del borde de la caja
background_stroke_widthint0Grosor del borde de la caja (0 = sin borde)
background_stroke_typestring"solid"solid, gradient2, instagram
background_stroke_dashstring"solid"Estilo de línea: solid, dashed, dotted

🖼️ Modelo — ImageOverlay

Superpone una imagen sobre la plantilla. Ideal para fotos de perfil del cliente, logos o imágenes generadas por IA.

CampoTipoReq.DefectoDescripción
srcstringURL HTTPS de la imagen o Data URL base64 (data:image/png;base64,...)
xint0Posición X en px
yint0Posición Y en px
widthint100Ancho en px
heightint100Alto en px
opacityfloat1.0Opacidad 0.0–1.0
rotationfloat0Rotación en grados
mask_typestring"none"Recorte: none, circle, ellipse, square, rect, star12
mask_radiusint0Radio de esquinas para mask_type: "rect"
mask_border_widthint0Grosor del borde alrededor del overlay (px)
mask_border_colorstring"#ffffff"Color del borde
mask_shadow_enabledboolfalseActiva sombra bajo el overlay
mask_shadow_blurint8Desenfoque de la sombra (px)

Ejemplo — foto de perfil circular

{
  "src": "https://cdn.example.com/foto-cliente.jpg",
  "x": 60, "y": 60,
  "width": 200, "height": 200,
  "mask_type": "circle",
  "mask_border_width": 4,
  "mask_border_color": "#FFFFFF",
  "mask_shadow_enabled": true,
  "mask_shadow_blur": 12
}

🔷 Modelo — CanvasShape

Forma geométrica renderizada debajo de los textos. Útil para crear fondos, marcos y divisores.

CampoTipoDefectoDescripción
shape_typestring"rect"rect, square, ellipse, circle, star12
x, yint0Posición en px
width, heightint100Dimensiones en px
fill_colorstring"#667eea"Color de relleno
fill_opacityfloat0.8Opacidad 0.0–1.0
stroke_colorstring"#000000"Color del borde
stroke_widthint0Grosor del borde (0 = sin borde)
rotationfloat0Rotación en grados
z_indexint0Orden de capas — mayor = encima
cover_blurint0Desenfoque gaussiano aplicado a la zona de la imagen debajo de la forma (glass morphism)

🔧 Variables dinámicas

Usa {nombre_variable} dentro de cualquier campo text de un TextField. El motor sustituye automáticamente cada llave con el valor correspondiente del objeto vars antes de renderizar.

{
  "texts": [
    { "text": "Hola, {nombre} 👋", "x": 100, "y": 80, "font_size": 64 },
    { "text": "Tu descuento: {descuento}", "x": 100, "y": 180, "font_size": 48 }
  ],
  "vars": {
    "nombre": "{{first name}}",    // custom field de ManyChat
    "descuento": "{{descuento_pct}}"
  }
}
Integración ManyChat: En el nodo "HTTP Request" de ManyChat, los custom fields se insertan con la sintaxis {{custom_field_name}}. TextOnFlow entonces sustituye {nombre} con el valor real del suscriptor.

⏱️ Contadores regresivos

Un TextField puede convertirse en un contador regresivo activando countdown_mode. El texto del campo se ignora y se reemplaza automáticamente por el tiempo restante.

Modo evento (fecha fija)

{
  "text": "",
  "x": 540, "y": 400,
  "font_size": 80,
  "font_color": "#FFFFFF",
  "countdown_mode": "event",
  "countdown_event_end_utc": "2026-12-31T23:59:59Z",
  "countdown_format": "DD:HH:MM:SS",
  "countdown_expired_text": "¡Oferta expirada!"
}

Modo urgencia (timer por suscriptor)

{
  "text": "",
  "x": 540, "y": 400,
  "countdown_mode": "urgency",
  "countdown_ts_var": "timer_final",  // nombre del custom field ManyChat
  "countdown_format": "HH:MM:SS",
  "countdown_urgency_hours": 3.0,        // cambia color al llegar a 3h
  "countdown_urgency_color": "#FF3B30"
}
countdown_formatEjemplo de salida
HH:MM:SS14:32:07
DD:HH:MM:SS03:14:32:07
HH:MM14:32

🎨 Filtros de imagen

El campo filter_name de MultiTextRequest aplica un filtro de color a la imagen base antes de renderizar los textos.

Instagram

clarendon
gingham
juno
lark
mayfair
moon
nashville
perpetua
reyes
rise
slumber
valencia
walden
xpro2
inkwell
toaster
lo_fi
hefe

Cinematográficos / LUT

bleach_bypass
candlelight
crisp_warm
crisp_winter
fall_colors
foggy_night
horror_blue
late_sunset
moonlight_ps
soft_warming
teal_orange
fuji_eterna
filmstock
tension_green
edgy_amber
drop_blues
2strip
3strip
futuristic
night_from_day
fuji_f125_2393
fuji_f125_2395
fuji_reala
kodak_5205
kodak_5218_2383
kodak_5218_2395

🌀 Efectos Warp (deformación de texto)

El campo warp_style de un TextField deforma el texto con distintas curvaturas. Combínalo con warp_bend (−100 a 100) para controlar la intensidad.

none
arc
arc_lower
arc_upper
arch
bulge
shell_lower
shell_upper
flag
wave
fish
rise
fisheye
inflate
squeeze
twist

🔤 Fuentes disponibles

Pasa el nombre exacto en el campo font_name del TextField. Todas las fuentes están precargadas en el servidor.

DejaVuSans-Bold
Montserrat-Bold
Montserrat-SemiBold
Montserrat-Regular
Oswald-Bold
Roboto-Bold
Roboto-Regular
Pacifico-Regular
BebasNeue-Regular
PlayfairDisplay-Bold
SourceSansPro-Bold
NotoSans-Bold
NotoSansArabic-Bold
NotoEmoji-Regular
Emojis: Todos los emojis Unicode son compatibles. El renderizador usa Noto Emoji automáticamente para los caracteres de emoji sin necesidad de configuración adicional.

🤖 Flujo ManyChat completo

Ejemplo de integración end-to-end: cuando un suscriptor activa el flujo, se genera una imagen personalizada y se envía por WhatsApp.

1

Nodo "Action" — Establecer custom field timer_final (solo si usas countdown urgencia)

Calcula el timestamp Unix de expiración y guárdalo en el custom field del suscriptor.

Fórmula ManyChat: {{NOW}} + 86400 (24 horas en segundos).

2

Nodo "HTTP Request" — Llamar a TextOnFlow

Method: POST
URL: https://www.textonflow.com/generate-multi
Headers: Content-Type: application/json

{
  "template_name": "mi-plantilla.jpg",
  "texts": [
    {
      "text": "Hola, {nombre} 🎉",
      "x": 540, "y": 120,
      "font_size": 72,
      "font_color": "#FFFFFF",
      "alignment": "center",
      "shadow_enabled": true,
      "shadow_blur": 6
    }
  ],
  "vars": {
    "nombre": "{{first name}}"
  }
}
Response mapping en ManyChat: Guarda el campo url de la respuesta en un custom field, por ejemplo imagen_url.
3

Nodo "Send Message" → Image

En el campo URL de la imagen, usa el custom field donde guardaste la respuesta:

{{imagen_url}}

ManyChat descargará la imagen de TextOnFlow y la enviará al suscriptor por WhatsApp.

4

¡Listo! 🎯

El suscriptor recibe una imagen personalizada con su nombre, datos de la oferta y/o contador regresivo en tiempo real — todo sin ningún diseñador ni exportación manual.

⚠️ Códigos de error

HTTPCódigoCausaSolución
200 OK Solicitud exitosa
400 Bad Request JSON inválido o campo requerido faltante Revisa que template_name y texts estén presentes
404 Not Found El archivo de plantilla no existe Verifica el nombre exacto del archivo en template_name
422 Unprocessable Entity Tipo de dato incorrecto (ej. string donde se espera int) Revisa los tipos del modelo. FastAPI devuelve detalles en detail
500 Internal Server Error Error en el renderizado (fuente no encontrada, imagen corrupta) Verifica el font_name y que la imagen base sea accesible
503 Service Unavailable Servidor en mantenimiento o reinicio Reintenta en 10–30 segundos

📏 Límites y buenas prácticas

Límites técnicos

  • Tamaño máximo recomendado de imagen base: 3840 × 3840 px (para render_scale 1)
  • Imágenes externas (overlays, template_name por URL): máximo 15 segundos de timeout de descarga
  • Tiempo de respuesta típico: 200–800 ms para plantillas locales con render_scale 1
  • La generación IA puede tardar 8–30 segundos — usa el sistema de polling

Buenas prácticas

  • Usa render_scale: 1 para flujos automáticos de ManyChat — es significativamente más rápido y el resultado es idéntico para pantallas móviles
  • Subir plantillas al editor en lugar de URLs externas — elimina el delay de descarga y es más estable
  • Tamaño recomendado de plantillas: 1080×1080 px para stories cuadradas, 1080×1920 px para vertical
  • Fonts: Usa Montserrat-Bold o BebasNeue-Regular para texto de marketing — mayor impacto visual
  • Overlays de fotos de perfil: Usa mask_type: "circle" con mask_border_width: 4 para un acabado profesional
  • Variables: Si un custom field puede estar vacío en ManyChat, define un fallback en el texto: {nombre|Cliente} — TextOnFlow usa el valor antes del | si está disponible
No uses la URL de imagen como CDN: Las imágenes generadas son temporales. Si tu cliente necesita guardar la imagen, hazlo desde el lado de ManyChat usando el nodo "Save Attachment" o descargándola en tu propio almacenamiento.