Paginación y sincronización incremental

Las listas se paginan con un cursor, del más reciente al más antiguo, hasta 100 registros por página. Combina updated_since con el cursor para mantener otro sistema sincronizado sin releer todo.

La API de SpaceKru (v1.1) es una API REST/JSON disponible en todos los planes de pago de SpaceKru. Sincroniza leads en ambos sentidos (upsert por external_id), añade leads a campañas, crea y mueve deals y pide seguimientos a los Empleados de IA, gestiona productos, cotizaciones, órdenes, pagos y reservas, permite que tu plataforma converse con tu Empleado de IA de Customer Service y envía webhooks firmados para 16 eventos que dicen quién causó cada cambio. URL base: https://app.spacekru.com/api/public/v1. Autenticación: llave de API Bearer (sk_live_…). Documentación: https://www.spacekru.com/es/developers. OpenAPI 3.1: https://app.spacekru.com/api/public/v1/openapi.json.

Read in English

Cómo se ve una lista

Cada endpoint de lista (GET /leads, /deals, /quotes, /orders, /products, /bookings, /campaigns…) responde con la misma forma:

GET /leads?limit=1
{
  "data": [
    {
      "object": "lead",
      "id": "3f2b8c1e-9a4d-4e6f-b7a8-1c2d3e4f5a6b",
      "type": "person",
      "full_name": "Maria Lopez",
      "business_name": null,
      "job_title": "Operations Manager",
      "work_email": "maria.lopez@example.com",
      "personal_email": null,
      "work_phone": "+13055550142",
      "personal_phone": null,
      "company_name": "Acme Corp",
      "city": "Miami",
      "country": "United States",
      "status": "new",
      "source": "api",
      "tags": [
        "typeform",
        "spring-promo"
      ],
      "instagram_profile": null,
      "linkedin_profile_url": null,
      "website": null,
      "external_id": "crm-88412",
      "created_at": "2026-10-19T14:32:10.000Z",
      "updated_at": "2026-10-19T14:32:10.000Z"
    }
  ],
  "has_more": true,
  "next_cursor": "eyJ0IjoiMjAyNi0xMC0xOVQxNDozMjoxMC4wMDBaIiwiaSI6IjNmMmI4YzFlLTlhNGQtNGU2Zi1iN2E4LTFjMmQzZTRmNWE2YiJ9"
}
  • data: los registros de esta página.
  • has_more: true cuando hay más registros después de esta página.
  • next_cursor: pásalo como cursor para la siguiente página. null en la última.

Parámetros

ParámetroDescripción
limitDe 1 a 100. Por defecto 25.
cursorEl next_cursor de la página anterior. Es opaco: no lo construyas ni lo modifiques. Un cursor inválido responde 400 INVALID_REQUEST.
updated_sinceSolo registros cuyo updated_at es igual o posterior a este momento. ISO 8601 con zona horaria, por ejemplo 2026-10-19T00:00:00Z.
statusCoincidencia exacta con status (por ejemplo won para deals, accepted para cotizaciones).
external_idCoincidencia exacta con tu propio id (leads, deals, cotizaciones, órdenes, productos, reservas).
typeperson o business (leads); el tipo de campaña (campañas).
lead_id, stage_id, resource_id, sku, from, toFiltros extra de deals, reservas y productos; mira cada lista en la referencia.

Los filtros se combinan con AND. Cualquier otro parámetro se rechaza con 400 (así un error de tipeo nunca devuelve todo en silencio).

Orden

  • Siempre del más reciente al más antiguo por created_at, luego por id, en todas las listas.
  • Los registros que se actualizan mientras recorres una lista no cambian de página.
  • Los registros creados mientras recorres una lista aparecen al inicio; una sincronización incremental (abajo) los recoge en la siguiente ejecución.

Lo que las listas dejan fuera

  • GET /orders no lista órdenes archivadas. Puedes leerlas por id y los webhooks las reportan.
  • GET /leads nunca lista los registros internos que SpaceKru guarda para su propio equipo.

Lista todo

Sigue next_cursor hasta que has_more sea false:

Node.js
const BASE = 'https://app.spacekru.com/api/public/v1';
const headers = { Authorization: `Bearer ${process.env.SPACEKRU_API_KEY}` };

// Lists EVERY record of a resource ('leads', 'deals', 'quotes', 'orders', 'products', 'bookings'…).
async function listAll(resource, params = {}) {
  const all = [];
  let cursor = null;
  do {
    const query = new URLSearchParams({ limit: '100', ...params });
    if (cursor) query.set('cursor', cursor);
    const res = await fetch(`${BASE}/${resource}?${query}`, { headers });
    const page = await res.json();
    if (!res.ok) throw new Error(`${page.error.code}: ${page.error.message}`);
    all.push(...page.data);
    cursor = page.has_more ? page.next_cursor : null;
  } while (cursor);
  return all;
}

const leads = await listAll('leads', { updated_since: '2026-10-01T00:00:00Z' });
console.log(`${leads.length} leads changed since October 1`);
Python
import os

import requests

BASE = "https://app.spacekru.com/api/public/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['SPACEKRU_API_KEY']}"}


def list_all(resource, **params):
    """Lists EVERY record of a resource ('leads', 'deals', 'quotes', 'orders', 'products', 'bookings'...)."""
    records, cursor = [], None
    while True:
        query = {"limit": 100, **params}
        if cursor:
            query["cursor"] = cursor
        r = requests.get(f"{BASE}/{resource}", headers=HEADERS, params=query, timeout=30)
        page = r.json()
        if not r.ok:
            raise RuntimeError(f"{page['error']['code']}: {page['error']['message']}")
        records.extend(page["data"])
        if not page["has_more"]:
            return records
        cursor = page["next_cursor"]


leads = list_all("leads", updated_since="2026-10-01T00:00:00Z")
print(f"{len(leads)} leads changed since October 1")

Recorrer 10.000 leads toma 100 solicitudes, muy por debajo del límite por minuto. Para trabajos más grandes, mira Límites de uso.

Sincronización incremental con updated_since

  1. Guarda la hora antes de que empiece la ejecución, no después, para recoger la próxima vez los cambios hechos durante la ejecución.
  2. Pide a cada recurso updated_since=<inicio de la última ejecución> y sigue el cursor.
  3. Haz upsert de cada registro por id. Los solapamientos no hacen daño: el mismo registro dos veces es el mismo upsert.
  4. Guarda la hora de inicio como updated_since de la siguiente ejecución.
Node.js — sincronización nocturna (usa listAll de arriba)
// Run it on a schedule (e.g. every night). `state` is wherever you keep the
// last run time: a database row, a file, a key-value store.
const startedAt = new Date().toISOString();              // 1. note the start time FIRST
const since = state.lastSync ?? '1970-01-01T00:00:00Z';

for (const resource of ['leads', 'deals', 'quotes', 'orders', 'products', 'bookings']) {
  const changed = await listAll(resource, { updated_since: since }); // 2. only what changed
  for (const record of changed) await db.upsert(resource, record.id, record); // 3. upsert by id
}

state.lastSync = startedAt;                              // 4. next run starts where this one began

Ejemplo de solicitud

curl
curl "https://app.spacekru.com/api/public/v1/deals?status=won&updated_since=2026-10-01T00%3A00%3A00Z&limit=100" \
  -H "Authorization: Bearer $SPACEKRU_API_KEY"
Node.js
const res = await fetch('https://app.spacekru.com/api/public/v1/deals?status=won&updated_since=2026-10-01T00%3A00%3A00Z&limit=100', {
  headers: {
    Authorization: `Bearer ${process.env.SPACEKRU_API_KEY}`,
  },
});
const body = await res.json();
if (!res.ok) {
  // Branch on body.error.code; quote body.error.request_id to support.
  throw new Error(`${body.error.code}: ${body.error.message} (request ${body.error.request_id})`);
}
console.log(body);
Python
import os

import requests

r = requests.get(
    "https://app.spacekru.com/api/public/v1/deals?status=won&updated_since=2026-10-01T00%3A00%3A00Z&limit=100",
    headers={
        "Authorization": f"Bearer {os.environ['SPACEKRU_API_KEY']}",
    },
    timeout=30,
)
body = r.json()
if not r.ok:
    # Branch on body["error"]["code"]; quote body["error"]["request_id"] to support.
    raise RuntimeError(f"{body['error']['code']}: {body['error']['message']} (request {body['error']['request_id']})")
print(body)