Saltar al contenido principal
Documentación para desarrolladores

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.com

Formato

application/json

Autenticació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 .

HTTP
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: read o write para 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

POST /api/api-keys
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: read permite a la clave leer (GET) todos los recursos de abajo; write le permite escribir (POST, PUT, PATCH, DELETE) en todos ellos.
  • Permisos por recurso: <resource>:read / <resource>:write dan 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 permisoQué cubreRutas
contacts:read
contacts:write
Contactos, importaciones y duplicados/api/v1/contacts, /api/entity/contact, /api/import, duplicates
companies:read
companies:write
Empresas/api/companies
deals:read
deals:write
Oportunidades y embudos/api/pipelines, /api/entity/opportunity
activities:read
activities:write
Actividades/api/activities, /api/entity/activity
tasks:read
tasks:write
Tareas y proyectos del espacio de trabajo/api/workspace
orders:read
orders:write
Pedidos, productos y suscripciones/api/v1/orders, /api/orders, /api/products, /api/unified-products, /api/import/orders, /api/entity/orders, subscriptions
bookings:read
bookings:write
Reservas/api/bookings
invoices:read
invoices:write
Facturas y proformas/api/invoicing
sales:read
sales:write
Presupuestos, pedidos de venta y albaranes/api/sales
treasury:read
treasury:write
Tesorería/api/treasury
schema:read
schema:write
Campos personalizados (esquema)/api/v1/schema, /api/schema
campaigns:read
campaigns:write
Registros de campañas de marketing/api/campaign
flow:read
flow:write
Journeys, segmentos y envíos masivos/api/flow
analytics:read
analytics:write
Analítica (solo lectura)/api/analytics, /api/dashboard
export:read
export:write
Exportación de datos (solo lectura)/api/export
media:read
media:write
Biblioteca multimedia/api/media
plugin:read
plugin:write
Sincronización de plugins / integraciones/api/plugin
crm:read
crm:write
Otros datos del CRM (búsqueda, estadísticas)/api/search, /api/entity (other)

Listar los permisos que se pueden dar a una clave

GET /api/api-keys/scopes
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.

403 insufficient_scope
# 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." }
GET/api/api-keys/scopes

Lista los recursos a los que se puede limitar una clave nueva. Requiere una sesión iniciada.

curl
curl -X GET "https://flowgration.com/api/api-keys/scopes" \
  -H "Authorization: Bearer sk_live_…"
POST/api/api-keys

Crea 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
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

GET /api/v1/me
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 } }
POST /api/v1/schema
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"}]
  }'
POST /api/v1/contacts/batch
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)" }
POST /api/v1/orders/batch
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:

Supported event names
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
POST/api/plugin/sync

Envía cualquier evento. Autenticación: Bearer sk_live_…

curl
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"
  }
}'
POST/api/plugin/sync

customer.created — crea o actualiza un Contacto (el email es la clave de deduplicación).

curl
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"
    }
  }
}'
POST/api/plugin/sync

product.created / product.updated — mantiene sincronizado el catálogo de productos del comerciante.

curl
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"
    ]
  }
}'
POST/api/plugin/sync

cart.abandoned — abre una Oportunidad en la etapa 'cart' y dispara el trigger de journey cart_abandoned.

curl
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
      }
    ]
  }
}'
POST/api/plugin/sync

form.submitted — captura de lead desde un formulario de marketing. Respeta tu política duplicate_handling.

curl
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"
  }
}'
POST/api/plugin/sync

order.refunded — actualiza payment_status y registra una actividad de devolución.

curl
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.

POST/api/plugin/orders/{source}

source = shopify | woocommerce | prestashop | magento | bigcommerce | wix | api | manual | csv_import

curl
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

Contact fields
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
GET/api/entity/contact

Lista contactos (paginado). Admite ?page=N&page_size=M&search=jane.

curl
curl -X GET "https://flowgration.com/api/entity/contact" \
  -H "Authorization: Bearer sk_live_…"
GET/api/entity/contact/{id}

Obtiene un contacto, con campos personalizados, campañas de origen y propietario.

curl
curl -X GET "https://flowgration.com/api/entity/contact/1" \
  -H "Authorization: Bearer sk_live_…"
POST/api/entity/contact

Crea un contacto. Crea automáticamente su primera Oportunidad y dispara los triggers contact_created + opportunity_created.

curl
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
  ]
}'
PATCH/api/entity/contact/{id}

Actualiza campos. Envía sólo los que cambian. Los custom_fields parciales se fusionan con los almacenados.

curl
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"
  }
}'
DELETE/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
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.

GET/api/entity/opportunity

Lista deals.

curl
curl -X GET "https://flowgration.com/api/entity/opportunity" \
  -H "Authorization: Bearer sk_live_…"
POST/api/entity/opportunity

Crea un deal asociado a un contacto.

curl
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"
  }
}'
PATCH/api/entity/opportunity/{id}

Cambia la etapa, el valor o el propietario. Dispara opportunity.stage_changed para los webhooks salientes.

curl
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
}'
DELETE/api/entity/opportunity/{id}

Elimina un deal. Si era la única oportunidad del contacto, se crea automáticamente una nueva vacía.

curl
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.

GET/api/entity/activity

Lista actividades. Filtrables por ?contact_id, ?opportunity_id, ?type, ?channel.

curl
curl -X GET "https://flowgration.com/api/entity/activity" \
  -H "Authorization: Bearer sk_live_…"
POST/api/entity/activity

Registra una actividad asociada a un contacto u oportunidad.

curl
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.

GET/api/orders

Lista pedidos desde la tabla de Pedidos de primera clase.

curl
curl -X GET "https://flowgration.com/api/orders" \
  -H "Authorization: Bearer sk_live_…"
GET/api/orders/{order_id}

Detalle completo del pedido (líneas, direcciones, devoluciones).

curl
curl -X GET "https://flowgration.com/api/orders/1" \
  -H "Authorization: Bearer sk_live_…"
POST/api/entity/orders

Creación directa en la superficie basada en Oportunidades (uso poco frecuente; usa /api/plugin/sync preferiblemente).

curl
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
    }
  ]
}'
PATCH/api/entity/orders/{order_id}

Actualiza el estado de pago o envío. Dispara el trigger order_paid al transicionar a 'paid'.

curl
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

GET/api/products

Lista los productos sincronizados en Flowgration.

curl
curl -X GET "https://flowgration.com/api/products" \
  -H "Authorization: Bearer sk_live_…"
GET/api/products/{product_id}

Obtiene un producto.

curl
curl -X GET "https://flowgration.com/api/products/1" \
  -H "Authorization: Bearer sk_live_…"
GET/api/products/{product_id}/orders

Pedidos que contienen este producto.

curl
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):

Allowed entity_type values
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).

GET/api/schema/{entity_type}

Lista las definiciones de campos personalizados para la entidad.

curl
curl -X GET "https://flowgration.com/api/schema/contact" \
  -H "Authorization: Bearer sk_live_…"
GET/api/schema/{entity_type}/system-fields

Lista 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
curl -X GET "https://flowgration.com/api/schema/contact/system-fields" \
  -H "Authorization: Bearer sk_live_…"
PUT/api/schema/{entity_type}

Reemplaza el esquema completo de la entidad. Requiere acceso de administrador o editor.

curl
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:

  1. Envía los nombres canónicos directamente. El endpoint POST /api/plugin/sync acepta 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_fields de la entidad correspondiente — no se descarta nada.
  2. Persiste un mapping_config en el servidor. Cada fila de integración en/app/integrations lleva un objetomapping_config JSON: { "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:

Trigger → fired by
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:

Inspect a journey run
curl "https://flowgration.com/api/entity/contact/1/timeline" \
  -H "Authorization: Bearer sk_live_…"

Onboarding del plugin (magic link)

POST/api/plugin/onboarding/initiate

Pú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
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
    }
  }
}'
GET/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.

Headers received by your endpoint
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.

Python (Flask)
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 "", 200
Node (Express)
const 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.updated
  • opportunity.created · opportunity.stage_changed
  • order.created · order.paid · order.shipped
  • task.created · task.completed
  • journey.completed · journey.failed
  • web.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.

web.page_viewed
{
  "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" }
}
web.custom_event
{
  "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" }
}
web.identified
{
  "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.

GET/api/webhook-endpoints

Lista tus endpoints registrados.

curl
curl -X GET "https://flowgration.com/api/webhook-endpoints" \
  -H "Authorization: Bearer sk_live_…"
POST/api/webhook-endpoints

Registra un nuevo endpoint. Devuelve el secreto de firma UNA sola vez.

curl
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
}'
PUT/api/webhook-endpoints/{id}

Actualiza los eventos, la URL o el flag active.

curl
curl -X PUT "https://flowgration.com/api/webhook-endpoints/1" \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "active": false
}'
DELETE/api/webhook-endpoints/{id}

Elimina el endpoint y su historial de entregas.

curl
curl -X DELETE "https://flowgration.com/api/webhook-endpoints/1" \
  -H "Authorization: Bearer sk_live_…"
POST/api/webhook-endpoints/{id}/rotate-secret

Genera un nuevo secreto HMAC. El anterior queda invalidado inmediatamente.

curl
curl -X POST "https://flowgration.com/api/webhook-endpoints/1/rotate-secret" \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{}'
POST/api/webhook-endpoints/{id}/test

Dispara un evento webhook.test sintético para verificar la firma de extremo a extremo.

curl
curl -X POST "https://flowgration.com/api/webhook-endpoints/1/test" \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{}'
GET/api/webhook-endpoints/{id}/deliveries

Últimos intentos de entrega (estado, extracto de respuesta, número de intento).

curl
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.

POST /api/webhook/form/{token} (no Authorization header)
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

Fields
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.

POST /api/v1/contacts/batch
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.

POST/api/contact-form/submit

Público, no requiere cabecera Authorization.

curl
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:

Response headers
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:

Paged response shape
{
  "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:

Generic error
{ "error": "code", "message": "Human-readable description" }
HTTPCuándo lo verás
400Petición incorrecta — cuerpo inválido, evento desconocido, entity_type desconocido o campo requerido faltante
401Bearer key ausente, inválida o revocada, o timestamp de webhook caducado
402Límite del plan alcanzado (p. ej. max_contacts, max_api_keys) — se requiere actualización
403Autenticado 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
404No encontrado, o filtrado por record_visibility para usuarios con rol de miembro
409Integración pausada (los eventos de sync se rechazan hasta reanudarla)
413Payload demasiado grande (límite de 256 KB en el schema del plugin)
429Límite de tasa alcanzado — espera y respeta la cabecera Retry-After
500Error 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