Referencia de la API REST
Crea cualquier integración. Autenticación con clave Bearer y permisos por recurso, JSON de entrada y salida, webhooks salientes firmados. Si desarrollas para una tienda a medida: carga el histórico con la API masiva v1 y envía los cambios en vivo a través de Ingesta de eventos — un único endpoint para cada evento de negocio.
URL base
https://flowgration.comFormato
application/jsonAutenticación
Bearer sk_live_…Autenticación
Todas las peticiones autenticadas deben incluir una cabecera Authorization con una Bearer key en el formato sk_live_…. Genera una desde Settings → Team & Access → API Keys en tu workspace, o accede directamente a /app/api-keys .
Authorization: Bearer sk_live_d3f8a5e6b9c… Content-Type: application/json
Cómo funcionan las claves
- Las claves son 32 bytes aleatorios con el prefijo
sk_live_; sólo se almacena un hash SHA-256 en el servidor. - El valor completo se muestra una sola vez al crearse — guárdalo en tu gestor de secretos de inmediato.
- Los permisos limitan lo que puede hacer una clave:
readowritepara toda la API de datos, o uno por recurso, como contacts:write. Consulta Permisos más abajo. El puente convierte tu clave en un JWT de 5 minutos internamente en cada llamada. - Puedes revocar temporalmente (y volver a activar) las claves desde la interfaz o mediante
PATCH /api/api-keys/{id}(con sesión iniciada). - Las claves se revocan al instante cuando se elimina o desactiva el usuario emisor, o cuando el workspace es cancelado.
- Plan gratuito: 1 clave activa. Planes de pago: ilimitadas.
Crear una clave por programación
curl -X POST "https://flowgration.com/api/api-keys" \
-H "Authorization: Bearer <session-JWT-of-an-owner-or-admin>" \
-H "Content-Type: application/json" \
-d '{"name":"My custom store","scopes":["contacts:write","orders:write","schema:write"]}'
# 201 response — copy the "key" field NOW, you cannot retrieve it again.
{ "id": 7, "name": "My custom store", "prefix": "sk_live_d3f",
"scopes": ["contacts:write","orders:write","schema:write"],
"revoked": false, "key": "sk_live_d3f8a5e6b9c…" }Crear, listar y revocar claves requiere una sesión iniciada de un propietario o administrador (Ajustes → Claves API). Una clave no puede gestionar claves. La creación está limitada a 10 por hora y organización.
Permisos de las claves (scopes)
Cada clave lleva una lista de permisos que eliges al crearla. Una petición se permite solo si la clave tiene el permiso del recurso que toca. El permiso es de lectura para GET y de escritura para el resto de métodos; analítica y exportación son de solo lectura sea cual sea el método.
Modelo de permisos
- Permisos generales:
readpermite a la clave leer (GET) todos los recursos de abajo;writele permite escribir (POST, PUT, PATCH, DELETE) en todos ellos. - Permisos por recurso:
<resource>:read/<resource>:writedan acceso a un solo recurso, por ejemplo contacts:write (crear y actualizar contactos) o invoices:read (leer facturas). Prefiere estos: una clave que solo sincroniza pedidos debería tener orders:write y nada más. - Los endpoints de administración nunca son accesibles con una clave. Las rutas de equipo, facturación, plan, ajustes, SSO, registro de auditoría, claves API, bandeja de entrada, RR. HH., IA y superadministración responden 403 insufficient_scope aunque la clave tenga
admin(se acepta por compatibilidad, no concede nada extra). - Claves antiguas: las claves creadas antes de que se aplicaran los permisos siguen funcionando como lectura + escritura sobre toda la API de datos, sean cuales sean los permisos que muestren. Pierden el acceso a los endpoints de administración. Crea una clave nueva con permisos más estrechos y revoca la antigua.
- Una clave actúa como el usuario que la creó: los permisos de módulo y de sección y la visibilidad de registros de ese usuario también se aplican a cada petición.
Recursos y rutas que cubren
| Recurso del permiso | Qué cubre | Rutas |
|---|---|---|
contacts:readcontacts:write | Contactos, importaciones y duplicados | /api/v1/contacts, /api/entity/contact, /api/import, duplicates |
companies:readcompanies:write | Empresas | /api/companies |
deals:readdeals:write | Oportunidades y embudos | /api/pipelines, /api/entity/opportunity |
activities:readactivities:write | Actividades | /api/activities, /api/entity/activity |
tasks:readtasks:write | Tareas y proyectos del espacio de trabajo | /api/workspace |
orders:readorders:write | Pedidos, productos y suscripciones | /api/v1/orders, /api/orders, /api/products, /api/unified-products, /api/import/orders, /api/entity/orders, subscriptions |
bookings:readbookings:write | Reservas | /api/bookings |
invoices:readinvoices:write | Facturas y proformas | /api/invoicing |
sales:readsales:write | Presupuestos, pedidos de venta y albaranes | /api/sales |
treasury:readtreasury:write | Tesorería | /api/treasury |
schema:readschema:write | Campos personalizados (esquema) | /api/v1/schema, /api/schema |
campaigns:readcampaigns:write | Registros de campañas de marketing | /api/campaign |
flow:readflow:write | Journeys, segmentos y envíos masivos | /api/flow |
analytics:readanalytics:write | Analítica (solo lectura) | /api/analytics, /api/dashboard |
export:readexport:write | Exportación de datos (solo lectura) | /api/export |
media:readmedia:write | Biblioteca multimedia | /api/media |
plugin:readplugin:write | Sincronización de plugins / integraciones | /api/plugin |
crm:readcrm:write | Otros datos del CRM (búsqueda, estadísticas) | /api/search, /api/entity (other) |
Listar los permisos que se pueden dar a una clave
curl "https://flowgration.com/api/api-keys/scopes" \
-H "Authorization: Bearer <session-JWT>"
# 200
{ "coarse": ["read", "write"],
"resources": [
{ "resource": "contacts", "description": "Contacts, imports and duplicates" },
{ "resource": "invoices", "description": "Invoices and proformas" },
… ] }Cuando falta un permiso
La respuesta indica el permiso que necesitaba la petición, para que lo añadas a una clave nueva.
# a key with only "contacts:read" calling POST /api/v1/contacts/batch
{ "error": "insufficient_scope",
"required_scope": "contacts:write",
"message": "This API key lacks the "contacts:write" scope." }
# any key calling an admin endpoint such as /api/team or /api/organization
{ "error": "insufficient_scope",
"message": "API keys cannot access this endpoint. Use a signed-in session." }/api/api-keys/scopesLista los recursos a los que se puede limitar una clave nueva. Requiere una sesión iniciada.
curl -X GET "https://flowgration.com/api/api-keys/scopes" \ -H "Authorization: Bearer sk_live_…"
/api/api-keysCrea una clave. Envía los permisos generales (read, write) y/o por recurso (contacts:write). Los permisos desconocidos devuelven 400 invalid_scopes. La clave completa se devuelve una sola vez. Requiere una sesión iniciada.
curl -X POST "https://flowgration.com/api/api-keys" \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"name": "Orders sync",
"scopes": [
"orders:write",
"contacts:write"
]
}'API de importación masiva (v1)
Para sistemas que se conectan por API sin plugin (ERP, tienda a medida, app propia). Envía hasta 500 registros por llamada; si reenvías los mismos registros se actualizan en lugar de duplicarse. Los campos desconocidos se guardan y se crean como campos personalizados automáticamente, así el CRM toma la forma de tus datos.
- GET /api/v1/me — prueba de conexión: organización, plan, recuentos y campos personalizados actuales.
- POST /api/v1/schema — declara tus campos personalizados por entidad (contact, order, opportunity). Añade o actualiza, nunca borra.
- POST /api/v1/contacts/batch — crea o actualiza contactos. Se emparejan por external_id y después por email.
- POST /api/v1/orders/batch — crea o actualiza pedidos con sus líneas. El cliente se convierte en contacto y los productos se crean a partir de las líneas. Los importes admiten decimales (49.99) o enteros *_cents.
Prueba de conexión
curl "https://flowgration.com/api/v1/me" -H "Authorization: Bearer sk_live_…"
# 200
{ "organization": { "id": 12, "name": "Acme", "plan": "crm_growth", "plan_status": "active" },
"user": { "id": 31, "email": "owner@acme.example" },
"counts": { "contacts": 1240, "orders": 5678, "products": 312 },
"fields": { "contact": [ … ], "opportunity": [ … ], "order": [ … ] },
"limits": { "max_batch": 500 } }curl -X POST "https://flowgration.com/api/v1/schema" \
-H "Authorization: Bearer sk_live_…" -H "Content-Type: application/json" \
-d '{
"contact": [
{"key": "loyalty_tier", "label": "Loyalty tier", "type": "select", "options": ["gold","silver"]},
{"key": "customer_since", "type": "date"}
],
"order": [{"key": "warehouse", "type": "text"}]
}'curl -X POST "https://flowgration.com/api/v1/contacts/batch" \
-H "Authorization: Bearer sk_live_…" -H "Content-Type: application/json" \
-d '{
"records": [
{"external_id": "CUST-1001", "email": "ana@example.com", "first_name": "Ana",
"last_name": "García", "phone": "+34600000000", "city": "Sevilla",
"tags": ["vip"], "loyalty_tier": "gold", "customer_since": "2021-03-14"}
]
}'
# 200 → { "created": 1, "updated": 0, "errors": 0, "fields_created": [],
# "results": [{ "index": 0, "id": 381, "status": "created" }] }
# 207 when some records failed, e.g.
# { "index": 3, "status": "error", "error": "plan contact limit reached (500)" }curl -X POST "https://flowgration.com/api/v1/orders/batch" \
-H "Authorization: Bearer sk_live_…" -H "Content-Type: application/json" \
-d '{
"records": [
{"external_id": "ORD-5531", "order_number": "5531",
"customer_email": "ana@example.com", "customer_name": "Ana García",
"financial_status": "paid", "currency": "EUR",
"total": 59.90, "subtotal": 49.90, "shipping": 10,
"placed_at": "2026-10-01T10:00:00Z",
"line_items": [{"sku": "TSHIRT-M", "name": "T-shirt", "quantity": 2, "unit_price": 24.95}],
"custom_fields": {"warehouse": "MAD"}}
]
}'Permisos: /me funciona con cualquier clave válida. /schema necesita schema:write, /contacts/batch necesita contacts:write y /orders/batch necesita orders:write (o el permiso general write).
Cada llamada admite hasta 500 registros (413 por encima). Los contactos nuevos respetan el límite de contactos de tu plan: los registros que lo superan devuelven status error y el resto se guardan igualmente.
Las importaciones masivas no disparan journeys por defecto (importar el histórico nunca debe enviar emails a miles de personas). Envía "trigger_journeys": true para disparar contact_created / order_created / order_paid en los registros NUEVOS.
Respuesta: 200 si todos los registros fueron bien, 207 si alguno falló — revisa results[] (index, id, status, error). Un registro erróneo nunca bloquea al resto.
Ingesta de eventos (el punto de entrada canónico)
Este es el endpoint que debe usar una tienda personalizada para el 95% de las escrituras. Un POST por evento de negocio: contacto creado, pedido realizado, producto actualizado, formulario enviado, carrito abandonado. La plataforma gestiona el upsert, la deduplicación y los disparadores de journey de forma automática.
Idempotencia. Pasa un external_id (el ID estable de tu tienda para la entidad, p. ej. "wc-1234"). Las repeticiones con la misma tupla (api_key, event, external_id) dentro de 24h devuelven {"deduplicated": true} y no vuelven a disparar los journeys.
Catálogo completo de eventos
Cualquier valor fuera de esta lista se rechaza con 400 unknown_event:
customer.created customer.updated customer.deleted order.created order.updated order.refunded order.return_requested order.return_approved order.return_declined order.return_cancelled order.return_closed cart.abandoned product.created product.updated product.deleted subscription.created subscription.updated subscription.cancelled subscription.renewed subscription.payment_failed booking.created booking.updated booking.cancelled form.submitted coupon.created coupon.updated coupon.deleted discount.created discount.updated discount.deleted membership.created membership.updated membership.cancelled review.created page.published page.updated store.updated fulfillment.created fulfillment.updated inventory.updated location.created location.updated location.deleted collection.created collection.updated collection.deleted metafield.created metafield.updated metafield.deleted plugin.heartbeat
/api/plugin/syncEnvía cualquier evento. Autenticación: Bearer sk_live_…
curl -X POST "https://flowgration.com/api/plugin/sync" \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"event": "order.created",
"platform": "custom",
"external_id": "ORD-12345",
"data": {
"order_number": "ORD-12345",
"customer_email": "jane@example.com",
"first_name": "Jane",
"last_name": "Doe",
"phone": "+34 600 123 456",
"total": 49.99,
"currency": "EUR",
"payment_status": "paid",
"shipping_status": "processing",
"line_items": [
{
"product_id": 42,
"sku": "WIDGET-RED",
"name": "Red Widget",
"quantity": 1,
"price": 49.99
}
],
"billing_address": {
"address_1": "Calle Mayor 1",
"city": "Madrid",
"postcode": "28001",
"country": "ES"
},
"utm_source": "google",
"utm_campaign": "spring_sale"
}
}'/api/plugin/synccustomer.created — crea o actualiza un Contacto (el email es la clave de deduplicación).
curl -X POST "https://flowgration.com/api/plugin/sync" \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"event": "customer.created",
"platform": "custom",
"external_id": "user_8801",
"data": {
"email": "jane@example.com",
"first_name": "Jane",
"last_name": "Doe",
"phone": "+34 600 123 456",
"phone_prefix": "+34",
"phone_number": "600123456",
"street": "Calle Mayor 1",
"city": "Madrid",
"country": "ES",
"postal_code": "28001",
"commercial_consent": true,
"tags": [
"newsletter"
],
"custom_fields": {
"loyalty_tier": "gold"
}
}
}'/api/plugin/syncproduct.created / product.updated — mantiene sincronizado el catálogo de productos del comerciante.
curl -X POST "https://flowgration.com/api/plugin/sync" \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"event": "product.updated",
"platform": "custom",
"external_id": "prod_42",
"data": {
"id": 42,
"sku": "WIDGET-RED",
"name": "Red Widget",
"description": "Premium red widget, 200g.",
"price": 49.99,
"currency": "EUR",
"image_url": "https://cdn.example.com/widget-red.jpg",
"stock": 120,
"status": "publish",
"categories": [
"widgets",
"red"
]
}
}'/api/plugin/synccart.abandoned — abre una Oportunidad en la etapa 'cart' y dispara el trigger de journey cart_abandoned.
curl -X POST "https://flowgration.com/api/plugin/sync" \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"event": "cart.abandoned",
"platform": "custom",
"external_id": "cart_9912",
"data": {
"customer_email": "jane@example.com",
"cart_total": 79.5,
"currency": "EUR",
"recovery_url": "https://store.example.com/cart?token=abc",
"line_items": [
{
"product_id": 42,
"name": "Red Widget",
"quantity": 1,
"price": 49.99
},
{
"product_id": 88,
"name": "Blue Widget",
"quantity": 1,
"price": 29.51
}
]
}
}'/api/plugin/syncform.submitted — captura de lead desde un formulario de marketing. Respeta tu política duplicate_handling.
curl -X POST "https://flowgration.com/api/plugin/sync" \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"event": "form.submitted",
"platform": "custom",
"external_id": "submission_771",
"data": {
"form_id": "contact-us",
"form_name": "Contact us",
"form_url": "https://www.example.com/contact",
"email": "jane@example.com",
"first_name": "Jane",
"last_name": "Doe",
"phone": "+34 600 123 456",
"message": "Interested in your enterprise plan.",
"utm_source": "facebook",
"utm_campaign": "leadgen_q2"
}
}'/api/plugin/syncorder.refunded — actualiza payment_status y registra una actividad de devolución.
curl -X POST "https://flowgration.com/api/plugin/sync" \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"event": "order.refunded",
"platform": "custom",
"external_id": "ORD-12345",
"data": {
"order_number": "ORD-12345",
"refund_amount": 49.99,
"currency": "EUR",
"reason": "Customer return"
}
}'Ingesta de pedidos (adaptadores de plataforma)
Si tu tienda ya habla el formato nativo de una plataforma conocida (Shopify, WooCommerce, PrestaShop, Magento, BigCommerce, Wix…) puedes enviar el payload nativo sin modificar a este endpoint y el adaptador correspondiente lo traducirá al formato canónico de Pedido + Contacto de Flowgration. Úsalo cuando estés redirigiendo un stream de webhooks existente que no puedes transformar.
Para tiendas personalizadas que no encajan en ninguno de esos formatos, utiliza preferiblemente POST /api/plugin/sync con event: order.created(la sección anterior) — acepta el formato canónico directamente.
/api/plugin/orders/{source}source = shopify | woocommerce | prestashop | magento | bigcommerce | wix | api | manual | csv_import
curl -X POST "https://flowgration.com/api/plugin/orders/shopify" \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"id": 12345,
"order_number": "ORD-12345",
"email": "jane@example.com",
"total_price": "49.99",
"currency": "EUR",
"line_items": [
{
"product_id": 42,
"title": "Red Widget",
"quantity": 1,
"price": "49.99"
}
],
"billing_address": {
"first_name": "Jane",
"last_name": "Doe",
"address1": "Calle Mayor 1",
"city": "Madrid",
"zip": "28001",
"country": "ES"
}
}'Contactos (CRUD directo)
Para la mayoría de los casos de uso, utiliza preferiblementePOST /api/plugin/sync con customer.created — crea automáticamente la Oportunidad asociada y dispara los triggers de journey. Estos endpoints CRUD directos son para herramientas de back-office, migraciones de datos o interfaces de administración.
Columnas de primera clase
first_name last_name email (dedupe key)
phone phone_prefix phone_number
street city country postal_code
website job_title company_name
age gender
form_id form_name form_url landing_page
referrer utm_source utm_medium utm_campaign
utm_term utm_content gclid
commercial_consent privacy_consent
owner_user_id tags ([str]) source_campaigns ([int])
custom_fields ({…}) notes/api/entity/contactLista contactos (paginado). Admite ?page=N&page_size=M&search=jane.
curl -X GET "https://flowgration.com/api/entity/contact" \ -H "Authorization: Bearer sk_live_…"
/api/entity/contact/{id}Obtiene un contacto, con campos personalizados, campañas de origen y propietario.
curl -X GET "https://flowgration.com/api/entity/contact/1" \ -H "Authorization: Bearer sk_live_…"
/api/entity/contactCrea un contacto. Crea automáticamente su primera Oportunidad y dispara los triggers contact_created + opportunity_created.
curl -X POST "https://flowgration.com/api/entity/contact" \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"first_name": "Ada",
"last_name": "Lovelace",
"email": "ada@example.com",
"phone": "+44 20 7946 0958",
"phone_prefix": "+44",
"phone_number": "2079460958",
"street": "12 St James's Square",
"city": "London",
"country": "GB",
"postal_code": "SW1Y 4LE",
"commercial_consent": true,
"custom_fields": {
"loyalty_tier": "gold",
"source_store": "uk-online"
},
"source_campaigns": [
42
]
}'/api/entity/contact/{id}Actualiza campos. Envía sólo los que cambian. Los custom_fields parciales se fusionan con los almacenados.
curl -X PATCH "https://flowgration.com/api/entity/contact/1" \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"phone": "+44 20 7946 9999",
"custom_fields": {
"loyalty_tier": "platinum"
}
}'/api/entity/contact/{id}Elimina permanentemente el contacto y todo lo asociado (oportunidades, actividades, ejecuciones de journey). Esta acción no se puede deshacer.
curl -X DELETE "https://flowgration.com/api/entity/contact/1" \ -H "Authorization: Bearer sk_live_…"
Oportunidades (deals y pipeline)
Una Oportunidad es un registro de deal / pipeline asociado a un contacto. Cada contacto tiene al menos una. Las etapas vienen del pipeline activo configurado en Customize CRM; el campo open | won | lost se deriva de los flags de la etapa a menos que lo establezcas explícitamente.
/api/entity/opportunityLista deals.
curl -X GET "https://flowgration.com/api/entity/opportunity" \ -H "Authorization: Bearer sk_live_…"
/api/entity/opportunityCrea un deal asociado a un contacto.
curl -X POST "https://flowgration.com/api/entity/opportunity" \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"contact_id": 1,
"title": "Q4 enterprise license",
"stage": "proposal",
"value": 12500,
"owner_user_id": 3,
"custom_fields": {
"competitor": "Acme",
"expected_close": "2026-08-31"
}
}'/api/entity/opportunity/{id}Cambia la etapa, el valor o el propietario. Dispara opportunity.stage_changed para los webhooks salientes.
curl -X PATCH "https://flowgration.com/api/entity/opportunity/1" \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"stage": "won",
"value": 14000
}'/api/entity/opportunity/{id}Elimina un deal. Si era la única oportunidad del contacto, se crea automáticamente una nueva vacía.
curl -X DELETE "https://flowgration.com/api/entity/opportunity/1" \ -H "Authorization: Bearer sk_live_…"
Actividades (línea de tiempo)
Las actividades son eventos de la línea de tiempo de un contacto u oportunidad: llamadas, emails, notas, reuniones, compras, pasos de journey. Úsalas para registrar cualquier cosa que deba aparecer en el historial del contacto sin modificar el CRM.
/api/entity/activityLista actividades. Filtrables por ?contact_id, ?opportunity_id, ?type, ?channel.
curl -X GET "https://flowgration.com/api/entity/activity" \ -H "Authorization: Bearer sk_live_…"
/api/entity/activityRegistra una actividad asociada a un contacto u oportunidad.
curl -X POST "https://flowgration.com/api/entity/activity" \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"contact_id": 1,
"type": "call",
"channel": "phone",
"title": "Discovery call",
"note": "Interested in the Plus plan. Follow up next Tuesday.",
"status": "completed",
"activity_metadata": {
"duration_seconds": 1820,
"recording_url": "https://…"
}
}'Pedidos (tabla de primera clase + legado basado en Oportunidades)
Flowgration expone los pedidos en dos superficies:
/api/orders— la tabla de Pedidos de primera clase (LTV, ingresos, exportaciones). Úsala para lecturas./api/entity/orders— la superficie legada basada en Oportunidades, mantenida para el motor de journeys y segmentos existente.
Para escrituras desde una tienda personalizada, no hagas POST a ninguna de las dos directamente — usaPOST /api/plugin/sync con event: order.created para que ambas superficies queden sincronizadas y los triggers order_paid / order_created se disparen correctamente.
/api/ordersLista pedidos desde la tabla de Pedidos de primera clase.
curl -X GET "https://flowgration.com/api/orders" \ -H "Authorization: Bearer sk_live_…"
/api/orders/{order_id}Detalle completo del pedido (líneas, direcciones, devoluciones).
curl -X GET "https://flowgration.com/api/orders/1" \ -H "Authorization: Bearer sk_live_…"
/api/entity/ordersCreación directa en la superficie basada en Oportunidades (uso poco frecuente; usa /api/plugin/sync preferiblemente).
curl -X POST "https://flowgration.com/api/entity/orders" \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"contact_id": 1,
"order_number": "INV-1234",
"total": 129.99,
"currency": "EUR",
"payment_status": "paid",
"items": [
{
"name": "Tea",
"qty": 2,
"price": 14.99
}
]
}'/api/entity/orders/{order_id}Actualiza el estado de pago o envío. Dispara el trigger order_paid al transicionar a 'paid'.
curl -X PATCH "https://flowgration.com/api/entity/orders/1" \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"payment_status": "paid",
"shipping_status": "delivered",
"tracking_number": "ABCD1234"
}'Productos
/api/productsLista los productos sincronizados en Flowgration.
curl -X GET "https://flowgration.com/api/products" \ -H "Authorization: Bearer sk_live_…"
/api/products/{product_id}Obtiene un producto.
curl -X GET "https://flowgration.com/api/products/1" \ -H "Authorization: Bearer sk_live_…"
/api/products/{product_id}/ordersPedidos que contienen este producto.
curl -X GET "https://flowgration.com/api/products/1/orders" \ -H "Authorization: Bearer sk_live_…"
Campos personalizados (esquema por entidad)
Los campos personalizados se definen por tipo de entidad y se almacenan como JSON en cada fila. El catálogo de entity_type es fijo (los errores tipográficos devuelven 400):
contact opportunity order activity booking campaign product activity_types
Importante — la separación entre order y opportunity. Antes del lanzamiento, los pedidos eran una sub-forma de Oportunidad, por lo que compartían el esquema de campos personalizados. Tras el lanzamiento son superficies distintas: un campo «order_priority» en order no aparece en opportunity, aunque ambas superficies acepten los mismos campos mapeados por el sistema (sku, total, currency…). Elige el entity_type que corresponda al lugar donde pertenece el campo.
Los valores de los campos personalizados se escriben a través del objeto custom_fields en la propia entidad (p. ej. POST /api/entity/contact con custom_fields: {loyalty_tier: "gold"}). Los endpoints siguientes te permiten leer / escribir el esquema (qué campos existen, qué tipos tienen, qué opciones ofrecen).
/api/schema/{entity_type}Lista las definiciones de campos personalizados para la entidad.
curl -X GET "https://flowgration.com/api/schema/contact" \ -H "Authorization: Bearer sk_live_…"
/api/schema/{entity_type}/system-fieldsLista de sólo lectura de los campos de sistema mapeados por la plataforma (sku, total, currency…). Útil para mostrar chips de «ya capturado» en tu interfaz.
curl -X GET "https://flowgration.com/api/schema/contact/system-fields" \ -H "Authorization: Bearer sk_live_…"
/api/schema/{entity_type}Reemplaza el esquema completo de la entidad. Requiere acceso de administrador o editor.
curl -X PUT "https://flowgration.com/api/schema/contact" \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"fields": [
{
"key": "loyalty_tier",
"label": "Loyalty tier",
"type": "select",
"options": [
"bronze",
"silver",
"gold",
"platinum"
]
},
{
"key": "lifetime_value_eur",
"label": "LTV (EUR)",
"type": "number"
}
]
}'Mapeo de campos (entrada desde tienda personalizada)
Cuando tu tienda no usa el plugin, tienes dos formas de mapear los campos entrantes a las columnas de contacto / pedido de Flowgration:
- Envía los nombres canónicos directamente. El endpoint
POST /api/plugin/syncacepta los nombres de campo listados en las secciones de Contactos e Ingesta de eventos (p. ej.first_name,email,phone,order_number,total,currency). Cualquier campo no reconocido va acustom_fieldsde la entidad correspondiente — no se descarta nada. - Persiste un mapping_config en el servidor. Cada fila de integración en
/app/integrationslleva un objetomapping_configJSON:{ "form_field_x": "first_name", "loyalty": "custom_fields.loyalty_tier" }. Las rutas con puntos apuntan a claves de campos personalizados. El plugin usa esto — una integración personalizada puede almacenar la misma estructura y aplicarla en el cliente antes de hacer POST con los payloads canónicos.
Los campos de sistema que siempre se mapean automáticamente (no es necesario declararlos) provienen de GET /api/schema/contact/system-fields para contactos y de GET /api/schema/order/system-fields para pedidos. Los alias habituales (billing_address.first_name, shipping_address.postcode…) se reconocen de forma nativa.
Disparadores de journey
Los journeys (automatizaciones) se inician automáticamente a partir de los eventos que envías. Hoy no existe un endpoint de matriculación manual — la plataforma dispara el trigger correspondiente dentro de la ruta relevante. Triggers que se disparan desde la ingesta por API:
contact_created ← POST /api/entity/contact
← customer.created (via /api/plugin/sync)
opportunity_created ← POST /api/entity/opportunity
← created automatically with every new contact
opportunity_stage_change ← PATCH /api/entity/opportunity/<id> (when 'stage' differs)
order_created ← order.created (via /api/plugin/sync)
order_paid ← order.updated → payment_status='paid'
← order.created with payment_status='paid'
order_status_changed ← order.updated / store sync when the order status changes
order_refunded ← order.refunded
cart_abandoned ← cart.abandoned
form_submission ← form.submitted
campaign_member ← contact created with source_campaigns: [id]Las llamadas masivas v1 solo lanzan journeys si envías trigger_journeys: true. order_status_changed se dispara cuando cambia el estado normalizado de un pedido (por ejemplo de processing a shipped) tras un evento order.updated o una sincronización de la tienda; las importaciones históricas nunca lo disparan.
El motor de journeys funciona de forma asíncrona: la respuesta de la API se devuelve inmediatamente tras persistir el evento; la evaluación del trigger ocurre en proceso en cuestión de milisegundos. Para verificar una ejecución, consulta la línea de tiempo del contacto:
curl "https://flowgration.com/api/entity/contact/1/timeline" \ -H "Authorization: Bearer sk_live_…"
Onboarding del plugin (magic link)
/api/plugin/onboarding/initiatePúblico. Modo automático: envía un magic link al administrador de la tienda (con límite de tasa por IP). Modo connect: asocia una nueva instalación del plugin a una organización existente mediante api_key.
curl -X POST "https://flowgration.com/api/plugin/onboarding/initiate" \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"platform": "woocommerce",
"platform_version": "8.5.0",
"store": {
"name": "Acme",
"url": "https://acme.example",
"currency": "EUR"
},
"admin": {
"email": "owner@acme.example",
"name": "Jane"
},
"schema": {
"customers": {
"count": 1234,
"fields": [
"email",
"phone",
"billing_address"
]
},
"orders": {
"count": 5678
}
}
}'/api/plugin/onboarding/verify?token={token}Landing del magic link público. Crea Org + Usuario + CRM, genera una API key y redirige a /plugin-callback.
Webhooks salientes
Registra endpoints HTTPS en Settings → Webhooks (o mediante POST /api/webhook-endpoints) para recibir eventos en tiempo real. Cada payload lleva tres cabeceras:
Los endpoints se gestionan desde la aplicación (o con una sesión iniciada). Las claves API no pueden crear ni modificar endpoints de webhooks.
X-Flowgration-Event: order.paid X-Flowgration-Timestamp: 1735000000 X-Flowgration-Signature: sha256=8f3b…d12c
Cómo verificar una firma
Calcula HMAC-SHA256(secret, "<timestamp>." + raw_body) y compara en tiempo constante con el valor sha256=…. Rechaza si el timestamp tiene más de 5 minutos ( 300s) — esa es la ventana de replay de Flowgration.
import hmac, hashlib, time
from flask import request, abort
SECRET = b"…" # the hex signing secret from Settings → Webhooks → Reveal secret
@app.post("/flowgration/webhook")
def receive():
ts = request.headers.get("X-Flowgration-Timestamp", "")
sig = request.headers.get("X-Flowgration-Signature", "").removeprefix("sha256=")
body = request.get_data() # raw bytes — DO NOT json.loads first
if abs(time.time() - int(ts)) > 300:
abort(401, "stale")
signed = ts.encode() + b"." + body
expected = hmac.new(SECRET, signed, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, sig):
abort(401, "bad signature")
event = request.headers["X-Flowgration-Event"]
payload = request.get_json()
# … handle event …
return "", 200const crypto = require("crypto");
const SECRET = process.env.FG_WEBHOOK_SECRET; // the hex signing secret (Reveal secret)
app.post("/flowgration/webhook",
express.raw({ type: "application/json" }), // raw body required
(req, res) => {
const ts = req.get("X-Flowgration-Timestamp");
const sig = (req.get("X-Flowgration-Signature") || "").replace(/^sha256=/, "");
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return res.status(401).end();
const expected = crypto.createHmac("sha256", SECRET)
.update(`${ts}.`).update(req.body).digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig))) {
return res.status(401).end();
}
const event = req.get("X-Flowgration-Event");
const payload = JSON.parse(req.body.toString("utf8"));
// … handle event …
res.status(200).end();
});Eventos disponibles
contact.created·contact.updatedopportunity.created·opportunity.stage_changedorder.created·order.paid·order.shippedtask.created·task.completedjourney.completed·journey.failedweb.session_started·web.page_viewed·web.identified·web.custom_event
Eventos de seguimiento web
Los cuatro eventos web.* proceden del tracker de tu web. web.page_viewed solo se envía para las rutas que indiques en los ajustes del sitio. Se firman y se reintentan como el resto de eventos, y requieren la analítica web de tu plan. El endpoint del tracker es interno: instala el fragmento de código en lugar de llamarlo.
{
"event": "web.page_viewed",
"occurred_at": "2026-11-09T10:15:00Z",
"site": { "id": 4, "name": "flowgration.example" },
"visitor": { "uid": "v_8c1f…", "contact_id": 381 },
"path": "/pricing",
"title": "Pricing",
"referrer_host": "google.com",
"utm": { "source": "google", "medium": "cpc", "campaign": "spring" }
}{
"event": "web.custom_event",
"occurred_at": "2026-11-09T10:16:20Z",
"site": { "id": 4, "name": "flowgration.example" },
"visitor": { "uid": "v_8c1f…", "contact_id": null },
"name": "pricing_toggle",
"properties": { "plan": "growth" }
}{
"event": "web.identified",
"occurred_at": "2026-11-09T10:18:02Z",
"site": { "id": 4 },
"visitor": { "uid": "v_8c1f…", "contact_id": 381 },
"contact": { "id": 381 },
"via": "form",
"first_seen_at": "2026-11-02T08:01:11Z",
"sessions": 3,
"page_views": 9
}Reintentos: 3 intentos con backoff exponencial (0,5s, 1s, 2s). Los endpoints que devuelven no-2xx (o superan el timeout de 10s) tres veces seguidas se deshabilitan automáticamente. Las URLs salientes están protegidas contra SSRF: las direcciones localhost, RFC1918, link-local y de metadatos cloud se rechazan en el momento de creación y de envío.
/api/webhook-endpointsLista tus endpoints registrados.
curl -X GET "https://flowgration.com/api/webhook-endpoints" \ -H "Authorization: Bearer sk_live_…"
/api/webhook-endpointsRegistra un nuevo endpoint. Devuelve el secreto de firma UNA sola vez.
curl -X POST "https://flowgration.com/api/webhook-endpoints" \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"url": "https://store.example.com/flowgration/webhook",
"events": [
"order.paid",
"order.shipped",
"contact.created"
],
"description": "Order + contact bridge",
"active": true
}'/api/webhook-endpoints/{id}Actualiza los eventos, la URL o el flag active.
curl -X PUT "https://flowgration.com/api/webhook-endpoints/1" \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"active": false
}'/api/webhook-endpoints/{id}Elimina el endpoint y su historial de entregas.
curl -X DELETE "https://flowgration.com/api/webhook-endpoints/1" \ -H "Authorization: Bearer sk_live_…"
/api/webhook-endpoints/{id}/rotate-secretGenera un nuevo secreto HMAC. El anterior queda invalidado inmediatamente.
curl -X POST "https://flowgration.com/api/webhook-endpoints/1/rotate-secret" \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{}'/api/webhook-endpoints/{id}/testDispara un evento webhook.test sintético para verificar la firma de extremo a extremo.
curl -X POST "https://flowgration.com/api/webhook-endpoints/1/test" \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{}'/api/webhook-endpoints/{id}/deliveriesÚltimos intentos de entrega (estado, extracto de respuesta, número de intento).
curl -X GET "https://flowgration.com/api/webhook-endpoints/1/deliveries" \ -H "Authorization: Bearer sk_live_…"
Recepción de formularios (cualquier herramienta)
Cualquier constructor de formularios, landing page o script que pueda enviar una petición HTTP puede alimentar tu CRM. Para el propio JotForm usa la integración dedicada, que registra el webhook por ti; para todo lo demás hay dos opciones.
Opción 1: webhook de formulario (sin clave API)
Envía las respuestas en JSON con POST a tu dirección privada de formularios. El contacto se crea o se actualiza por email, se añade una entrada de Envío de formulario a su cronología, recibe una oportunidad si no tenía y arranca cada journey cuyo disparador sea Envío de formulario (filtra por form_id; el journey dispone de form_name y de los campos).
Tu dirección es /api/webhook/form/<token>. Cópiala en la app, en Apps conectadas, JotForm, Otras herramientas de formularios, o léela con GET /api/webhook/token (sesión iniciada). POST /api/webhook/token genera un token nuevo e invalida la dirección anterior. El token es un secreto: envía desde un servidor o desde los ajustes de webhook de una herramienta de formularios y vuelve a generarlo si se filtra.
curl -X POST "https://app.flowgration.com/api/webhook/form/<your_webhook_token>" \
-H "Content-Type: application/json" \
-d '{
"email": "ana@example.com",
"first_name": "Ana",
"last_name": "García",
"phone": "+34 600 000 000",
"form_name": "Contact form",
"form_id": "contact-1",
"form_url": "https://example.com/contact",
"source_channel": "website",
"utm_source": "google", "utm_medium": "cpc", "utm_campaign": "spring",
"fields": { "message": "I would like a quote", "budget": "5000" }
}'
# 201 new contact / 200 existing contact
# { "status": "ok", "contact_id": 381, "is_new_contact": true,
# "opportunity_id": 90, "activity_id": 4012 }
# 400 { "error": "email is required" } 401 { "error": "Invalid webhook token" }Campos del cuerpo
email (required, the dedupe key) first_name last_name phone
form_name (timeline + journeys) form_id (journeys can filter on it) form_url
source_channel utm_source utm_medium utm_campaign
fields ({ any extra answers, stored as custom fields on the contact })
fg_vid (optional: the visitor id from the website tracker, links the visit history to the contact; never stored as a field)Herramientas como Typeform, Tally o Google Forms envían su propio formato de datos, no este JSON. Apúntalas a un pequeño intermediario (Zapier, Make, n8n o una función tuya) que traduzca sus respuestas al cuerpo de arriba.
Opción 2: API de contactos por lotes (clave API)
Con una clave API que tenga el ámbito contacts:write, envía con POST hasta 500 contactos de una vez. Es idempotente (se cruza por external_id y luego por email) y, por defecto, no activa journeys, así que es la herramienta adecuada para importaciones. Añade "trigger_journeys": true si quieres que se ejecuten.
curl -X POST "https://flowgration.com/api/v1/contacts/batch" \
-H "Authorization: Bearer sk_live_…" -H "Content-Type: application/json" \
-d '{ "records": [
{ "email": "ana@example.com", "first_name": "Ana", "tags": ["web-form"],
"message": "I would like a quote" }
] }'Formulario de marketing público (sin autenticación)
Endpoint sin autenticación para formularios de contacto en landing pages. Limitado a 5 envíos por IP cada 10 minutos; el campo honeypot hp_company_url descarta bots silenciosamente.
/api/contact-form/submitPúblico, no requiere cabecera Authorization.
curl -X POST "https://flowgration.com/api/contact-form/submit" \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"name": "Jane",
"last_name": "Doe",
"email": "jane@example.com",
"phone": "+34 600 123 456",
"subject": "Enterprise enquiry",
"body": "Tell me about pricing for 50 seats.",
"callback_requested": true,
"source_page": "/pricing",
"source_cta": "pricing_enterprise_cta"
}'Límites de tasa, paginación y errores
Límites de tasa
El límite por defecto es de 200 peticiones por minuto. Se cuenta por usuario: una clave cuenta contra el usuario que la creó, así que varias claves de la misma persona comparten un solo cupo. Las cabeceras de cada respuesta te dicen dónde estás:
X-RateLimit-Limit: 200 # max calls allowed per minute X-RateLimit-Remaining: 164 # calls left in the current window X-RateLimit-Reset: 1735000060 # unix-seconds when the window resets
Superar el límite devuelve 429 Too Many Requests con una cabecera Retry-After (en segundos). Algunos endpoints públicos tienen throttles adicionales: /api/plugin/onboarding/initiate está limitado a 30 llamadas por IP por hora; /api/contact-form/submit a 5 por IP cada 10 minutos.
Paginación
Los endpoints de listado aceptan ?page=N&limit=M con valores por defecto page=1, limit=50. El envelope de respuesta:
{
"items": [ … ],
"page": 1,
"limit": 50,
"total": 1247
}Para extracciones muy grandes, reduce el conjunto de resultados en el servidor con ?search= (y los demás filtros de listado) en lugar de paginación profunda — los números de página altos están permitidos pero son más lentos.
Modelo de errores
Todos los errores devuelven JSON con la siguiente forma:
{ "error": "code", "message": "Human-readable description" }| HTTP | Cuándo lo verás |
|---|---|
400 | Petición incorrecta — cuerpo inválido, evento desconocido, entity_type desconocido o campo requerido faltante |
401 | Bearer key ausente, inválida o revocada, o timestamp de webhook caducado |
402 | Límite del plan alcanzado (p. ej. max_contacts, max_api_keys) — se requiere actualización |
403 | Autenticado pero la clave o el usuario no tiene el permiso del módulo, la clave no tiene el scope necesario (insufficient_scope), el endpoint no está disponible para claves API o la lista de IP permitidas lo deniega |
404 | No encontrado, o filtrado por record_visibility para usuarios con rol de miembro |
409 | Integración pausada (los eventos de sync se rechazan hasta reanudarla) |
413 | Payload demasiado grande (límite de 256 KB en el schema del plugin) |
429 | Límite de tasa alcanzado — espera y respeta la cabecera Retry-After |
500 | Error del servidor — contacta con support@flowgration.com si persiste |
SDKs y Plugins
Los plugins de primera parte viven en su propio repositorio — son la forma más fácil de conectar una tienda a Flowgration sin escribir código de API:
- • WordPress / WooCommerce — construye el CRM automáticamente + sincronización en tiempo real mediante hooks de WP
- • Shopify — Embedded App con OAuth + bridge nativo de webhooks
- • Próximamente: PrestaShop, Wix, BigCommerce, Magento, Squarespace, Webflow, Ghost, Typeform
¿Necesitas una plataforma que no está en la lista? Construye la tuya con esta API REST — todos los plugins de nuestro monorepo son open source y siguen la misma especificación compartida (github.com/jaimearribas1/flowgration-plugins), y pueden usarse como punto de partida al hacer un fork.
Consigue una API key en 30 segundos
Genera una clave sk_live_ desde tu workspace, define los scopes y empieza a integrar.
Prueba gratis