Joinet · Documentación técnica

API ELE GATE

Acceso de solo lectura al catálogo ELE GATE: precios de todas las listas, existencia por tienda, fichas técnicas, imágenes y datos del punto de venta. El dato se toma directo de MyBusinessPOS, que se revisa cada minuto.

3,699Artículos
2,710Publicados
1,975Con existencia
11:56 p.m.Última revisión del POS

Horario de Guadalajara · último cambio en artículos ELE GATE: 1 ago 2026, 11:32 p.m.

Empezar en un minuto

Todas las direcciones cuelgan de https://elegate.joinet.com, siempre por HTTPS y siempre con el método GET. Pega esto en una terminal cambiando TU_LLAVE y ya tienes datos:

curl -H "X-API-Key: TU_LLAVE" \
     "https://elegate.joinet.com/api/v1/productos?por_pagina=3"

Si la llave es correcta, la respuesta empieza con "ok": true. Si algo falla, empieza con "ok": false y trae el motivo.

Autenticación

Cada petición necesita una llave. Se manda en una cabecera, de cualquiera de estas dos formas, la que le acomode a tu programa:

X-API-Key: jelg_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Authorization: Bearer jelg_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

La llave se muestra una sola vez. En el servidor solo se guarda su huella criptográfica, así que no se puede recuperar después: si se pierde, se revoca y se genera otra.

Trátala como contraseña. No la pongas en código que corra en un navegador ni la subas a un repositorio: quien la tenga puede leer todo el catálogo.

Cada llave lleva su propio registro de uso —cuántas peticiones, cuándo fue la última y desde qué dirección— y puede revocarse o ponerse con fecha de vencimiento en cualquier momento.

Forma de las respuestas

Siempre es JSON con Content-Type: application/json. Las respuestas correctas traen "ok": true; los errores traen "ok": false, una clave error estable para programar contra ella, y un mensaje en español para leerlo.

Dos detalles que conviene saber al procesar el JSON:

Límites y frescura del dato

Cada llave tiene un tope de peticiones por hora. Toda respuesta trae estas cabeceras:

CabeceraQué dice
X-RateLimit-LimitTu tope por hora.
X-RateLimit-RemainingCuántas peticiones te quedan en esta hora.
X-RateLimit-ResetSegundos que faltan para que el contador vuelva a cero.
X-Request-IdIdentificador de esta llamada. Si reportas un problema, mándalo: con él se encuentra la petición exacta en la bitácora.

Si se agota, la respuesta es 429 e incluye Retry-After con los segundos exactos que hay que esperar: no hace falta adivinar.

El sincronizador revisa el punto de venta cada minuto y hace una pasada completa a las 3 de la mañana. Ojo con una diferencia que confunde: solo escribe los artículos que cambiaron. Entonces:

Dicho de otro modo: un synced_at viejo no quiere decir dato viejo, quiere decir dato que no ha cambiado. Para saber si la cadena está corriendo, mira ultima_revision_pos.

Todas las fechas se entregan en formato ISO 8601 con zona horaria (2026-08-01T14:37:37.000Z, es decir UTC). El portal las muestra convertidas a horario de Guadalajara.

El máximo por página es 200. Para recorrer todo el catálogo, pagina con pagina; para mantenerte al día sin bajar todo, filtra con desde.

Endpoints

Son cuatro. Cualquier otro método que no sea GET responde 405.

GET/api/v1/estatus

Salud del servicio, datos de tu llave y conteos del catálogo. Es la forma más rápida de comprobar que una llave sirve y qué tan fresco está el dato.

curl -H "X-API-Key: TU_LLAVE" "https://elegate.joinet.com/api/v1/estatus"
{
  "ok": true,
  "servicio": "joinet-elegate-api",
  "version": "1.0.0",
  "llave": {
    "nombre": "Joinet interno",
    "prefijo": "jelg_c7e1b2c3",
    "incluye_costos": true,
    "limite_hora": 2000
  },
  "catalogo": {
    "total": 3699, "publicados": 2710, "privados": 989,
    "con_stock": 1975, "descontinuados": 405,
    "ultima_revision_pos": "2026-08-02T05:12:41.224Z",
    "ultimo_dato_nuevo":   "2026-08-01T20:37:40.872Z",
    "ultimo_cambio_pos":   "2026-08-01T14:37:37.000Z"
  },
  "hora_servidor": "2026-08-02T04:39:20.011Z"
}
GET/api/v1/categorias

Las 81 categorías presentes en el catálogo ELE GATE, ordenadas por número de artículos. El codigo que devuelve se usa tal cual en el filtro categoria del listado.

{
  "ok": true,
  "total": 81,
  "categorias": [
    { "codigo": "JUGUE", "nombre": "Juguetes",                  "productos": 277, "con_stock": 149 },
    { "codigo": "AHOGA", "nombre": "Artículos para el hogar",   "productos": 264, "con_stock": 141 },
    { "codigo": "CABLE", "nombre": "Cables y conectores",       "productos": 194, "con_stock": 122 }
  ]
}
GET/api/v1/productos

El listado. Todos los parámetros son opcionales y se combinan entre sí.

ParámetroTipoPor omisiónQué hace
qtextoBusca el texto en descripción, SKU y claves alternas.
categoriatextoCódigo o nombre de categoría. Los códigos salen de /categorias.
statustextotodospublish, private o draft.
skustextoHasta 200 SKU separados por coma. Trae solo esos, en una sola llamada.
campostextotodosQué campos devolver, separados por coma. Aligera muchísimo la respuesta.
sucursaltextoUna, varias con coma, o un grupo: todas · web · tiendas · cedis. Agrega stock_sucursal a cada producto.
con_stock1 / 00Con 1 devuelve solo lo que tiene existencia. Si mandas sucursal, mide la existencia de esa sucursal.
con_imagenes1 / 00Con 1 agrega el arreglo imagenes a cada producto del listado.
descontinuado1 / 0ambosCon 0 excluye los descontinuados; con 1 trae solo esos.
desdefecha ISOSolo lo modificado o sincronizado a partir de esa fecha.
ordentextoskusku · descripcion · precio · precio_desc · stock · stock_desc · venta_desc · modificado_desc · alta_desc
paginaentero1Número de página. Empieza en 1.
por_paginaentero50Resultados por página, de 1 a 200.
curl -H "X-API-Key: TU_LLAVE" \
     "https://elegate.joinet.com/api/v1/productos?q=cable&con_stock=1&orden=venta_desc&por_pagina=1"
{
  "ok": true,
  "paginacion": { "total": 170, "pagina": 1, "por_pagina": 1, "paginas": 170 },
  "productos": [
    {
      "sku": "ADP.HDTV.RJ45",
      "descripcion": "Extensor repetidor hdmi de 60 metros a cable ethernet rj45…",
      "ficha_tecnica": "Es un dispositivo que permite transmitir señales…",
      "status": "publish",
      "precio": "168.0000",
      "precios_lista": { "1": 168, "2": 165, "3": 162, "4": 153, "5": 153, "6": 133 },
      "en_oferta": false,
      "stock_total": 114,
      "stock_web": 217,
      "stock_sucursales": { "independencia": 103, "septiembre": 84, "cotilla": 30, "corona": 0 },
      "marca": "ELE-GATE",
      "categoria": "Redes",
      "categoria_codigo": "REDES",
      "codigos_barras": ["0.64688"],
      "cantidad_caja": "100.0000",
      "unidad": "PZA",
      "clave_sat": "43222608",
      "garantia": true,
      "venta_total": 579,
      "costos": { "c1": 14, "c2": 0, "c3": 0, "c4": 0, "ultimo": 14 },
      "proveedores": { "1": "ELE GATE", "2": null, "3": null },
      "modificado_pos": "2026-08-01T14:37:37.000Z",
      "synced_at": "2026-08-01T20:37:40.872Z"
    }
  ]
}

El bloque paginacion te dice cuántos resultados hay en total y cuántas páginas son con el tamaño que pediste. Cuando pagina se pasa del final, productos llega vacío: ese es el punto para dejar de pedir.

Pedir solo los campos que ocupas

Una página completa de 200 productos pesa unos 464 KB, y buena parte son fichas técnicas. Si solo necesitas precio y existencia, con campos la misma página baja a 26 KB: 95% menos.

https://elegate.joinet.com/api/v1/productos?campos=sku,precio,stock_total,stock_sucursales&por_pagina=200

El sku siempre se incluye aunque no lo pidas, porque sin él la respuesta no identifica nada. Un campo que no exista devuelve 400 con la lista completa de los válidos.

Varios SKU en una sola llamada

En lugar de pedir producto por producto, mándalos juntos. Máximo 200 por petición, y no distingue mayúsculas:

https://elegate.joinet.com/api/v1/productos?skus=WI.62,CH.60,PT.10

Fotos en el listado

Con con_imagenes=1 cada producto trae su arreglo imagenes, igual que en el detalle. Es la forma de armar un catálogo con fotos sin hacer una llamada por artículo:

https://elegate.joinet.com/api/v1/productos?con_imagenes=1&con_stock=1&por_pagina=100

Buscar por código de barras

q busca al mismo tiempo en la descripción, el SKU, las claves alternas y los códigos de barras. Un código escaneado se pega tal cual:

https://elegate.joinet.com/api/v1/productos?q=0.64688
GET/api/v1/productos/{sku}

Un artículo con todo lo disponible: los mismos campos del listado, más todas sus fotos y la descripción larga que se muestra en la tienda. El SKU no distingue mayúsculas de minúsculas.

curl -H "X-API-Key: TU_LLAVE" "https://elegate.joinet.com/api/v1/productos/FES.26.8"
{
  "ok": true,
  "producto": { "sku": "FES.26.8", "descripcion": "Listón decorativo…", "…": "los 41 campos" },
  "imagenes": [
    { "url": "https://cdn.joinet.com/…/FES.26.8.webp", "posicion": 0, "es_principal": true },
    { "url": "https://cdn.joinet.com/…/FES.0.webp",    "posicion": 1, "es_principal": false }
  ],
  "contenido_web": {
    "descripcion_web": "<h1>Listón decorativo brilloso, 2.7 metros…</h1>",
    "videos": [],
    "updated_at": "2026-08-01T08:30:15.999Z"
  }
}

En imagenes, la que trae es_principal: true es la portada; las demás vienen en el orden en que se muestran. contenido_web llega como null si ese artículo no tiene descripción propia en la tienda.

GET/api/v1/skus

La lista completa de SKU vigentes, sin paginar y sin más datos. Pesa poco y sirve para una cosa que ningún otro endpoint resuelve: enterarte de las bajas.

Por qué existe. El filtro desde te trae altas y cambios, pero si un artículo se borra o deja de ser ELE GATE, simplemente deja de aparecer — nadie te avisa. Si mantienes una copia, se te queda con productos fantasma para siempre.

La solución: pide esta lista, compárala contra tus SKU y lo que no venga aquí, dalo de baja.

{
  "ok": true,
  "total": 3699,
  "generado": "2026-08-02T05:31:12.004Z",
  "skus": ["0269", "0515", "0649", "1717", "ADP.HDTV.RJ45", "..."]
}
# Qué SKU tengo yo que ya no existan del otro lado
curl -s -H "X-API-Key: TU_LLAVE" "https://elegate.joinet.com/api/v1/skus" \
  | jq -r '.skus[]' | sort > vigentes.txt
comm -23 mis-skus.txt vigentes.txt   # esto es lo que hay que dar de baja

Listas de precio

Cada producto trae hasta diez listas en precios_lista. No son categorías de cliente: son cortes por cantidad. Mientras más piezas lleva el cliente, menor es el precio por pieza.

ListaAplica a partir deQué es
11 piezaPrecio público de menudeo. Es el mismo valor del campo precio.
22 piezasPrimer escalón por volumen.
33 a 5 piezasSegundo escalón.
46 a 8 piezasTercer escalón.
59 a 11 piezasCuarto escalón.
612 piezas o másPrecio de mayoreo. Es el más bajo de la escalera.
7 a 10Existen en el punto de venta pero no se usan en la web. Casi siempre llegan en 0.

Un 0 no es gratis: significa que esa lista no tiene precio capturado. Al calcular, ignora las listas en 0 y usa la anterior que sí tenga valor.

"precios_lista": { "1": 168, "2": 165, "3": 162, "4": 153, "5": 153, "6": 133, "7": 0, "8": 0, "9": 0, "10": 0 }

// Un cliente que lleva 7 piezas paga la lista 4: $153 cada una.
// Uno que lleva 20 paga la lista 6: $133 cada una.

Existencias por sucursal

Las existencias se pueden ver de dos maneras, y las dos están disponibles al mismo tiempo.

Todas juntas: no hay que pedir nada

Cada producto ya trae el desglose completo, siempre. stock_sucursales son las cuatro tiendas y stock_cedis los cuatro centros de distribución:

"stock_total": 7081,
"stock_web":   8732,
"stock_sucursales": { "independencia": 1651, "septiembre": 4808, "cotilla": 2273, "corona": 0 },
"stock_cedis":      { "alcalde": 0, "madero": 0, "prisciliano": 0, "cedis4": 0 }

Una sucursal en particular

Con el parámetro sucursal. Cada producto agrega el campo stock_sucursal con la existencia de lo que pediste, y si combinas con con_stock=1 el filtro se aplica a esa sucursal, no al total:

curl -H "X-API-Key: TU_LLAVE" \
  "https://elegate.joinet.com/api/v1/productos?sucursal=cotilla&con_stock=1&orden=stock_desc&por_pagina=3"
{
  "ok": true,
  "paginacion": { "total": 1399, "pagina": 1, "por_pagina": 3, "paginas": 466 },
  "sucursales": ["cotilla"],
  "productos": [
    {
      "sku": "JUG.G888",
      "stock_sucursal": 8495,
      "stock_total": 9717,
      "stock_sucursales": { "independencia": 131, "septiembre": 1091, "cotilla": 8495, "corona": 0 }
    },
    {
      "sku": "CH.60",
      "stock_sucursal": 2273,
      "stock_total": 8732,
      "stock_sucursales": { "independencia": 1651, "septiembre": 4808, "cotilla": 2273, "corona": 0 }
    }
  ]
}

Con orden=stock_desc, el orden también respeta la sucursal pedida: arriba queda lo que más hay en Cotilla, no lo que más hay en total.

Varias sucursales sumadas

Separadas por coma. stock_sucursal devuelve la suma de esas ubicaciones:

https://elegate.joinet.com/api/v1/productos?sucursal=cotilla,septiembre&con_stock=1&orden=stock_desc
"sucursales": ["cotilla", "septiembre"],
"productos": [
  { "sku": "JUG.G888", "stock_sucursal": 9586 },   // 8495 en Cotilla + 1091 en Septiembre
  { "sku": "CH.60",    "stock_sucursal": 7081 }    // 2273 + 4808
]

Grupos con nombre

Tres atajos para no escribir la lista completa:

GrupoIncluyePara qué sirve
todaslas 4 tiendas + los 4 CEDISTodo junto, en un solo número. Es la existencia real en toda la empresa.
webseptiembre · cotilla · independenciaLas tres tiendas que sí surten pedidos en línea. Es el mismo criterio del campo stock_web.
tiendasindependencia · septiembre · cotilla · coronaLas cuatro tiendas, sin contar centros de distribución.
cedisalcalde · madero · prisciliano · cedis4Solo los centros de distribución.
https://elegate.joinet.com/api/v1/productos?sucursal=todas&con_stock=1&orden=stock_desc
https://elegate.joinet.com/api/v1/productos?sucursal=web&con_stock=1
https://elegate.joinet.com/api/v1/productos?sucursal=cedis&con_stock=1

Los grupos también se combinan con sucursales sueltas: sucursal=cedis,cotilla suma los cuatro centros de distribución más la tienda de Cotilla.

Nombres válidos

Tiendas: independencia · septiembre · cotilla · corona
Centros de distribución: alcalde · madero · prisciliano · cedis4
Grupos: todas · web · tiendas · cedis

Si mandas un nombre que no existe, la respuesta es 400 con la lista completa de opciones en el mensaje, así que no hace falta adivinar.

Ejemplo completo: inventario de una tienda

Todo lo que hay hoy en Independencia, ordenado de mayor a menor existencia, listo para pegar en una hoja de cálculo:

curl -s -H "X-API-Key: TU_LLAVE" \
  "https://elegate.joinet.com/api/v1/productos?sucursal=independencia&con_stock=1&orden=stock_desc&por_pagina=200" \
| jq -r '.productos[] | [.sku, .descripcion, .stock_sucursal, .precio] | @csv' \
> inventario-independencia.csv

Diccionario de campos

Estos son los campos que trae cada producto, tanto en el listado como en el detalle.

CampoTipoSignificadoEjemplo
skutextoClave del artículo en el POS. Es el identificador único y el que se usa en /productos/{sku}."WI.62"
descripciontextoDescripción principal, tal como está capturada en el POS."1pza Cable trifásico…"
descripcion_alternatextoSegunda descripción. Suele venir vacía.null
ficha_tecnicatextoFicha larga: características, especificaciones, modo de uso y cuidados. Texto plano con saltos de línea."Cable trifásico de…"
statustextoEstado en la tienda: publish (visible), private (oculto) o draft."publish"
block_reasontextoMotivo por el que el artículo está bloqueado, cuando aplica.null
precionúmeroPrecio público vigente. Es el mismo que precios_lista["1"]."168.0000"
precios_listaobjetoPrecios de las listas 1 a 10, con el número de lista como llave. Un 0 significa que esa lista no tiene precio.{"1":168,"6":133}
tipo_preciotextoLetra de la política de precio del POS (A, B, C…). null = precio manual.null
precio_validosí/nofalse cuando el POS no tiene un precio utilizable para el artículo.true
en_ofertasí/noEl artículo está marcado en oferta.false
en_rematesí/noEl artículo está marcado en remate.false
precio_regular_ofertanúmeroPrecio tachado que acompaña a la oferta."168.0000"
leyenda_ofertatextoTexto de la promoción capturado en el POS."Precio Normal"
stock_totalenteroExistencia total según el campo del POS, incluye ubicaciones no vendibles.114
stock_webenteroExistencia vendible en línea: suma de Septiembre, Cotilla e Independencia. Puede no coincidir con stock_total porque son dos cálculos distintos del POS.217
stock_sucursalesobjetoExistencia por tienda: independencia, septiembre, cotilla y corona.{"cotilla":30,…}
stock_cedisobjetoExistencia en centros de distribución: alcalde, madero, prisciliano y cedis4.{"alcalde":0,…}
marcatextoNombre de la marca."ELE-GATE"
marca_codigotextoCódigo de la marca en el POS."ELEGA"
marca_imagentextoURL del logotipo de la marca."https://cdn.joinet…"
categoriatextoNombre de la línea o categoría."Redes"
categoria_codigotextoCódigo de la categoría. Es el que se manda en el filtro categoria."REDES"
codigos_barraslistaHasta cuatro códigos de barras del artículo.["0.64688"]
claves_alternastextoClaves alternas separadas por coma: claves del fabricante y equivalencias. La búsqueda q también busca aquí.null
cantidad_cajanúmeroPiezas por caja o empaque."100.0000"
unidadtextoUnidad de medida."PZA"
clave_sattextoClave del producto en el catálogo del SAT, para facturación."43222608"
garantiasí/noEl artículo maneja garantía.true
descontinuadosí/noEstá marcado como descontinuado en el POS.false
resurtirsí/noEstá marcado para resurtido.true
bloqueado_busquedasí/noExcluido del buscador de la tienda.false
imagen_postextoURL de la imagen principal registrada en el POS. Para todas las fotos, usa el detalle."https://cdn.joinet…"
fecha_creacion_posfechaFecha de alta del artículo en el POS."2025-08-14T00:00:00Z"
modificado_posfechaÚltima modificación registrada en el POS. Es el campo natural para detectar cambios."2026-08-01T14:37:37Z"
synced_atfechaMomento exacto en que este servidor tomó el registro del POS."2026-08-01T20:37:40Z"

Campos internos

Estos solo se entregan si la llave tiene habilitado el acceso interno. Con una llave sin ese permiso simplemente no aparecen en la respuesta —no llegan vacíos, no llegan en cero: no llegan.

CampoTipoSignificadoEjemplo
costosobjetoCostos c1 a c4 y costo último. Un 0 significa que ese costo no está capturado.{"c1":14,"ultimo":14}
venta_totalenteroPiezas vendidas acumuladas históricas.579
venta_sucursalesobjetoPiezas vendidas acumuladas por tienda.{"cotilla":70,…}
proveedoresobjetoProveedor 1, 2 y 3. El POS admite hasta tres por artículo.{"1":"ELE GATE","2":null}
ultima_compra_posfechaFecha de la última compra registrada en el POS."2024-08-09T00:00:00Z"

Errores

HTTPerrorQué pasó
400orden_invalido · fecha_invalidaUn parámetro no tiene el formato esperado. El mensaje dice cuál y qué se acepta.
401sin_llave · llave_invalidaNo mandaste llave, o no corresponde a ninguna registrada.
403llave_revocada · llave_vencidaLa llave existe pero ya no sirve.
404no_encontradoEl SKU no existe dentro del catálogo ELE GATE.
405metodo_no_permitidoSe usó POST, PUT, PATCH o DELETE. Esta API es de solo lectura.
429limite_excedidoSe superó el límite de peticiones por hora de la llave.
{
  "ok": false,
  "error": "limite_excedido",
  "mensaje": "Superaste 2000 peticiones en esta hora. Espera al siguiente bloque horario."
}

Casos de uso

Qué hay disponible para vender hoy

https://elegate.joinet.com/api/v1/productos?con_stock=1&status=publish&orden=venta_desc

Buscar por texto, código de barras o clave del fabricante

El parámetro q busca al mismo tiempo en la descripción, en el SKU y en las claves alternas.

https://elegate.joinet.com/api/v1/productos?q=hdmi
https://elegate.joinet.com/api/v1/productos?q=WI.62

Todo lo de una categoría

https://elegate.joinet.com/api/v1/categorias
https://elegate.joinet.com/api/v1/productos?categoria=REDES&por_pagina=200

Solo lo que cambió desde ayer

Es la forma correcta de mantener una copia al día sin volver a bajar los 3,699 artículos. Guarda la fecha de tu última corrida y mándala en desde.

https://elegate.joinet.com/api/v1/productos?desde=2026-08-01T00:00:00Z&por_pagina=200

Lo más vendido que ya se está acabando

https://elegate.joinet.com/api/v1/productos?con_stock=1&orden=venta_desc&por_pagina=50

Ejemplos por lenguaje

Terminal (curl)

curl -s -H "X-API-Key: TU_LLAVE" \
  "https://elegate.joinet.com/api/v1/productos?con_stock=1&por_pagina=5" | jq '.productos[] | {sku, precio, stock_total}'

PHP

<?php
function elegate($ruta) {
    $ch = curl_init("https://elegate.joinet.com/api/v1" . $ruta);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER     => ["X-API-Key: TU_LLAVE"],
        CURLOPT_TIMEOUT        => 30,
    ]);
    $r = json_decode(curl_exec($ch), true);
    curl_close($ch);
    if (empty($r["ok"])) throw new Exception($r["mensaje"] ?? "Error de la API");
    return $r;
}

$r = elegate("/productos?con_stock=1&por_pagina=20");
echo "Hay {$r['paginacion']['total']} artículos con existencia\n";
foreach ($r["productos"] as $p) {
    printf("%-16s %-50s $%8.2f  stock %d\n",
        $p["sku"], mb_substr($p["descripcion"], 0, 50),
        (float) $p["precio"], $p["stock_total"]);
}

JavaScript (Node, no navegador)

Esta API no se llama desde el navegador, a propósito. No manda cabeceras CORS, así que un fetch desde una página web falla — y está bien que falle: para que funcionara, la llave tendría que ir escrita en el código de la página, donde cualquiera la lee abriendo el inspector. Llámala siempre desde tu servidor y que él le pase los datos a la página.

const API = "https://elegate.joinet.com/api/v1";
const LLAVE = "TU_LLAVE";

async function elegate(ruta) {
  const r = await fetch(API + ruta, { headers: { "X-API-Key": LLAVE } });
  const d = await r.json();
  if (!d.ok) throw new Error(d.mensaje);
  return d;
}

const { productos, paginacion } = await elegate("/productos?q=cable&con_stock=1");
console.log(`${paginacion.total} resultados`);
productos.forEach(p =>
  console.log(p.sku, parseFloat(p.precio).toFixed(2), p.stock_total));

Python

import requests

API = "https://elegate.joinet.com/api/v1"
CAB = {"X-API-Key": "TU_LLAVE"}

r = requests.get(f"{API}/productos", headers=CAB,
                 params={"con_stock": 1, "orden": "venta_desc", "por_pagina": 20},
                 timeout=30)
d = r.json()
if not d["ok"]:
    raise SystemExit(d["mensaje"])

for p in d["productos"]:
    print(f'{p["sku"]:<16} {p["descripcion"][:50]:<52} '
          f'${float(p["precio"]):>8.2f}  stock {p["stock_total"]}')

Bajar todo el catálogo

Se pagina de 200 en 200 hasta que una página llegue vacía. Son 19 peticiones para los 3,699 artículos, muy por debajo del límite por hora.

pagina=1
: > catalogo.jsonl
while :; do
  r=$(curl -s -H "X-API-Key: TU_LLAVE" \
      "https://elegate.joinet.com/api/v1/productos?por_pagina=200&pagina=$pagina")
  n=$(echo "$r" | jq '.productos | length')
  [ "$n" -eq 0 ] && break
  echo "$r" | jq -c '.productos[]' >> catalogo.jsonl
  echo "página $pagina · $n artículos"
  pagina=$((pagina + 1))
done
echo "Total: $(wc -l < catalogo.jsonl) artículos"

Lo mismo en Python, con reintentos

import requests, json, time

API, CAB = "https://elegate.joinet.com/api/v1", {"X-API-Key": "TU_LLAVE"}
pagina, todos = 1, []

while True:
    for intento in range(3):
        try:
            d = requests.get(f"{API}/productos", headers=CAB,
                             params={"por_pagina": 200, "pagina": pagina},
                             timeout=60).json()
            break
        except requests.RequestException:
            time.sleep(2 ** intento)
    else:
        raise SystemExit(f"Falló la página {pagina}")

    if not d["productos"]:
        break
    todos += d["productos"]
    print(f'página {pagina} · {len(todos)}/{d["paginacion"]["total"]}')
    pagina += 1

with open("catalogo.json", "w", encoding="utf-8") as f:
    json.dump(todos, f, ensure_ascii=False, indent=2)

Probar sin escribir código

Dos archivos listos para descargar, con las 14 peticiones de ejemplo ya armadas. Solo pones tu llave en la variable y le das enviar.

ArchivoPara quéCómo se usa
elegate.postman.jsonPostmanPostman → Import → arrastra el archivo. Luego edita la variable llave de la colección.
elegate.httpVS Code · JetBrainsÁbrelo con la extensión REST Client, cambia @llave y pulsa "Send request" arriba de cada petición.

Cómo conseguir una llave

Las llaves las emite el equipo de sistemas de Joinet. Al pedirla, di para qué es y desde dónde se va a usar, para asignarle el límite adecuado y decidir si lleva acceso a datos internos.

Al crearla se define: nombre (para identificarla después), si incluye datos internos (costos, ventas y proveedores), el límite por hora y si vence en cierta fecha. Todo eso se puede consultar en cualquier momento en /api/v1/estatus, y una llave se puede revocar al instante si se filtra.

Lo que esta API no hace

Para que nadie pierda tiempo buscándolo:

Versiones y cambios

La versión va en la dirección: /api/v1/. El compromiso es simple:

VersiónFechaCambios
1.0.01 ago 2026Primera versión: estatus, categorías, listado, detalle y SKU vigentes; filtros por sucursal, campos, SKU múltiples, imágenes y descontinuados.