API 4pricing para desarrolladores
Conecta tu propio sistema (CRM, ERP, tienda online) a 4pricing y consulta los precios de la competencia, los grupos de comparación y las alertas directamente desde tu código.
URL base: https://4pricing.net/api/v1/pricing · Especificación OpenAPI (JSON)
Integración rápida en 3 pasos
Esta es la forma más corta de ver tus datos de 4pricing en tu propia web. Copiarás un archivo, pegarás en él tu clave y lo subirás a tu hosting: sin frameworks ni instalaciones.
- Una suscripción activa a 4pricing (la API está incluida en todos los planes).
- Un hosting con PHP: casi todos los hostings compartidos lo tienen (cPanel, Plesk y similares).
- Una forma de subir un archivo: el Administrador de archivos de tu hosting o un programa FTP.
Crea tu clave de API
Entra en panel.4pricing.net y abre Monitorización de precios → Claves API.
Haz clic en “Generar clave”, ponle un nombre (por ejemplo “mi tienda”) y copia la clave que empieza por prc_live_. Solo se muestra una vez: si la pierdes, crea otra y revoca la anterior.
Copia el archivo ya preparado y pega tu clave
Copia el archivo PHP de abajo en un editor de texto (Bloc de notas, TextEdit o VS Code) y guárdalo como 4pricing-precios.php.
php4pricing-precios.php
<?php // ===================================================================== // PEGA AQUÍ TU CLAVE DE API (panel.4pricing.net → Claves API), entre las comillas $apiKey = 'PEGA_AQUI_TU_CLAVE'; // ===================================================================== header('Content-Type: text/html; charset=utf-8'); function h($text) { return htmlspecialchars((string) $text, ENT_QUOTES, 'UTF-8'); } function money($cents) { if ($cents === null) { return '—'; } return number_format($cents / 100, 2, ',', '.'); } if (strpos($apiKey, 'prc_') !== 0) { exit('<p>' . h('Falta la clave de API: abre el archivo y pega tu clave en la línea marcada.') . '</p>'); } $ch = curl_init('https://4pricing.net/api/v1/pricing/groups/'); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_USERAGENT => 'my-shop-integration/1.0', CURLOPT_TIMEOUT => 30, CURLOPT_HTTPHEADER => ['X-API-Key: ' . $apiKey, 'Accept: application/json'], ]); $body = curl_exec($ch); $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); if ($body === false) { exit('<p>' . h('No se pudo conectar con 4pricing:' . ' ' . curl_error($ch)) . '</p>'); } if ($status !== 200) { $hints = [401 => 'la clave de API es incorrecta o fue revocada', 402 => 'la cuenta no tiene una suscripción activa a 4pricing (o no tiene créditos suficientes)', 429 => 'demasiadas peticiones en un minuto, inténtalo de nuevo dentro de un minuto']; $hint = isset($hints[$status]) ? ' — ' . $hints[$status] : ''; exit('<p>' . h('4pricing respondió con el error' . ' ' . $status . $hint) . '</p><pre>' . h($body) . '</pre>'); } $groups = json_decode($body, true); $verdicts = [ 'cheapest' => 'Eres el más barato', 'too_cheap' => 'Mucho más barato que el mercado: puedes subir el precio', 'competitive' => 'Competitivo', 'too_expensive' => 'Más caro que la mayoría de los competidores', 'no_competitors' => 'Aún no hay precio de la competencia', 'no_own_price' => 'Falta tu precio', ]; echo '<!doctype html><html><head><meta charset="utf-8"><title>' . h('Mis productos monitorizados') . '</title>'; echo '<style>body{font-family:Arial,sans-serif;margin:24px}table{border-collapse:collapse}'; echo 'th,td{border:1px solid #ddd;padding:6px 10px;text-align:left}th{background:#f5f5f5}</style>'; echo '</head><body><h1>' . h('Mis productos monitorizados') . '</h1>'; if (!$groups) { echo '<p>' . h('Todavía no tienes productos comparados.') . '</p>'; } else { echo '<table><tr><th>' . h('Producto') . '</th><th>' . h('SKU') . '</th>'; echo '<th>' . h('Tu precio') . '</th><th>' . h('Competidor más barato') . '</th>'; echo '<th>' . h('Diferencia') . '</th><th>' . h('Veredicto') . '</th></tr>'; foreach ($groups as $g) { $yours = $g['own_price_cents_effective']; $cheapest = $g['min_competitor_cents']; $diff = '—'; if ($yours !== null && $cheapest !== null) { $diff = ($yours > $cheapest ? '+' : '') . money($yours - $cheapest); } $verdict = isset($verdicts[$g['verdict']]) ? $verdicts[$g['verdict']] : $g['verdict']; echo '<tr><td>' . h($g['name']) . '</td><td>' . h($g['sku']) . '</td>'; echo '<td>' . money($yours) . '</td><td>' . money($cheapest) . '</td>'; echo '<td>' . $diff . '</td><td>' . h($verdict) . '</td></tr>'; } echo '</table>'; } echo '</body></html>';Sustituye PEGA_AQUI_TU_CLAVE por tu clave, manteniendo las comillas que la rodean. Es la única línea que tienes que cambiar.Sube el archivo y ábrelo en el navegador
Sube 4pricing-precios.php a tu web con el Administrador de archivos o por FTP, en la misma carpeta que tu sitio (normalmente public_html).
Abre https://tu-sitio/4pricing-precios.php en el navegador. Verás una tabla con cada producto comparado: tu precio, el precio más bajo de la competencia, la diferencia y el veredicto.
El archivo: lista tus productos con el precio más bajo de la competencia
El archivo pide a 4pricing tus productos comparados (GET /groups/) y los muestra en una tabla. Leer datos es gratis y no consume créditos.
- “Tu precio” es el precio que 4pricing conoce para ti; “Competidor más barato” es el precio más bajo encontrado entre los competidores que monitorizas.
- Los precios se muestran en unidades (12999 céntimos pasan a ser 129,99); un guion (—) significa que 4pricing aún no tiene precio, no que sea cero.
- Si en lugar de la tabla ves un error, el mensaje te indica qué corregir: clave incorrecta, suscripción no activa o demasiadas peticiones en un minuto.
Segunda receta: enviar tu precio
Úsala cuando 4pricing deba conocer tu propio precio sin leerlo de la página de tu tienda, por ejemplo directamente desde la base de datos de tu tienda. Enviar tus precios es gratis (sin créditos) y no cuenta para el límite de URL monitorizadas de tu plan.
| Campo | Significado |
|---|---|
| sku | el código de tu producto; 4pricing encuentra el producto por él, o lo crea la primera vez |
| name | el nombre del producto, tal como quieres verlo en 4pricing |
| url | opcional: la dirección de la página del producto en tu tienda (nunca se lee) |
| price_cents | el precio multiplicado por 100, sin decimales: 49,99 → 4999 |
| currency | el código de la moneda, por ejemplo EUR, GBP o RON (RON si falta) |
php4pricing-enviar-precio.php
<?php
// =====================================================================
// PEGA AQUÍ TU CLAVE DE API (panel.4pricing.net → Claves API), entre las comillas
$apiKey = 'PEGA_AQUI_TU_CLAVE';
// =====================================================================
// TUS PRODUCTOS: un bloque por producto. price_cents = precio x 100 (49,99 pasa a ser 4999).
$products = [
[
'sku' => 'CAMISETA-BLANCA-M',
'name' => 'Camiseta blanca, talla M',
'url' => 'https://tu-tienda.example/camiseta-blanca-m',
'price_cents' => 4999,
'currency' => 'EUR',
],
];
header('Content-Type: text/html; charset=utf-8');
function h($text) { return htmlspecialchars((string) $text, ENT_QUOTES, 'UTF-8'); }
if (strpos($apiKey, 'prc_') !== 0) {
exit('<p>' . h('Falta la clave de API: abre el archivo y pega tu clave en la línea marcada.') . '</p>');
}
$ch = curl_init('https://4pricing.net/api/v1/pricing/own-products/bulk');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_USERAGENT => 'my-shop-integration/1.0',
CURLOPT_TIMEOUT => 60,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['X-API-Key: ' . $apiKey, 'Content-Type: application/json', 'Accept: application/json'],
CURLOPT_POSTFIELDS => json_encode(['items' => $products], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($body === false) {
exit('<p>' . h('No se pudo conectar con 4pricing:' . ' ' . curl_error($ch)) . '</p>');
}
if ($status !== 200) {
$hints = [401 => 'la clave de API es incorrecta o fue revocada', 402 => 'la cuenta no tiene una suscripción activa a 4pricing (o no tiene créditos suficientes)', 429 => 'demasiadas peticiones en un minuto, inténtalo de nuevo dentro de un minuto'];
$hint = isset($hints[$status]) ? ' — ' . $hints[$status] : '';
exit('<p>' . h('4pricing respondió con el error' . ' ' . $status . $hint) . '</p><pre>' . h($body) . '</pre>');
}
$result = json_decode($body, true);
echo '<h1>' . h('Precios enviados a 4pricing') . '</h1>';
echo '<p>' . h('Productos nuevos creados:') . ' ' . (int) $result['created'] . '</p>';
echo '<p>' . h('Productos actualizados:') . ' ' . (int) $result['updated'] . '</p>';
if (!empty($result['errors'])) {
echo '<p>' . h('Filas con problemas:') . '</p><ul>';
foreach ($result['errors'] as $error) {
echo '<li>' . h($error) . '</li>';
}
echo '</ul>';
}
- Guárdalo como 4pricing-enviar-precio.php, pega tu clave, edita las filas de productos, súbelo y ábrelo una vez: la página muestra cuántos productos se han creado y cuántos actualizado.
- Cada petición puede llevar hasta 2.000 productos; una fila incorrecta se notifica y las demás se guardan igualmente. Si vuelves a enviar el mismo sku, el precio se actualiza.
Las mismas llamadas con curl
Si prefieres la terminal, estos dos comandos hacen exactamente lo mismo que los dos archivos PHP:
bash
curl https://4pricing.net/api/v1/pricing/groups/ \
-H "X-API-Key: PEGA_AQUI_TU_CLAVE"bash
curl -X POST https://4pricing.net/api/v1/pricing/own-products/bulk \
-H "X-API-Key: PEGA_AQUI_TU_CLAVE" \
-H "Content-Type: application/json" \
-d '{"items":[{"sku":"CAMISETA-BLANCA-M","name":"Camiseta blanca, talla M","url":"https://tu-tienda.example/camiseta-blanca-m","price_cents":4999,"currency":"EUR"}]}'- La clave da acceso a toda tu cuenta de 4pricing. Guárdala solo en archivos del lado del servidor (PHP, tu backend), nunca en JavaScript del navegador ni en un repositorio público.
- Cualquiera que conozca la dirección de la página subida puede ver la tabla de precios (no la clave). Ponle al archivo un nombre difícil de adivinar, protege la carpeta con contraseña o bórralo cuando ya no lo necesites.
- Si una clave se filtra, revócala en Claves API —deja de funcionar al instante— y crea una nueva.
Autenticación
- Cada petición lleva tu clave en la cabecera X-API-Key. Authorization: Bearer prc_live_… también funciona (solo para las claves que empiezan por prc_).
- No hay permisos por ámbito (scopes): una clave puede leer y modificar todo en tu cuenta de 4pricing. Crea una clave por integración (tienda, ERP, informe) para poder revocar una sin detener las demás.
- Una clave ausente, incorrecta o revocada recibe 401. La revocación surte efecto de inmediato. Demasiadas claves no válidas en un minuto reciben 429.
- Las claves se crean, se listan y se revocan solo en el panel (Monitorización de precios → Claves API); la propia API no puede gestionar claves. Crear una clave requiere una suscripción activa a 4pricing.
http
X-API-Key: prc_live_…
# or
Authorization: Bearer prc_live_…URL base y convenciones
- Cada ruta de esta documentación es relativa a la URL base: /groups/ significa https://4pricing.net/api/v1/pricing/groups/.
- Las peticiones y las respuestas son JSON (Content-Type: application/json).
- Los importes son números enteros en la unidad menor (céntimos): price_cents = 12999 significa 129,99. Los porcentajes van en puntos básicos (100 = 1 %).
- Un valor desconocido es null, nunca 0: muéstralo como “—”.
- Las colecciones se llaman con barra final (/products/, /groups/, /insights/).
- Las listas paginadas devuelven el total en la cabecera X-Total-Count (por ejemplo GET /products/?skip=0&limit=200).
- Envía una cabecera User-Agent que identifique tu integración (por ejemplo my-shop-integration/1.0). El cortafuegos que hay delante de la API responde 403 a algunos valores genéricos por defecto de las librerías, como Python-urllib.
Límite de peticiones, suscripción y límites del plan
Límite de peticiones
- 60 peticiones por minuto por CUENTA, en una ventana deslizante: todas las claves de la misma cuenta comparten esas mismas 60.
- Cada respuesta incluye X-RateLimit-Limit, X-RateLimit-Remaining y X-RateLimit-Reset (la duración de la ventana, en segundos).
- Si superas el límite recibes 429 con una cabecera Retry-After (segundos). Espera ese tiempo y vuelve a intentarlo: los ejemplos de más abajo lo hacen por ti.
http
HTTP/2 200
x-ratelimit-limit: 60
x-ratelimit-remaining: 59
x-ratelimit-reset: 60
HTTP/2 429
retry-after: 60
{"detail":"Rate limit exceeded"}Suscripción y límites del plan
- La API requiere una suscripción activa a 4pricing; sin ella, cada petición recibe 402 con el código plan_required.
- Todos los planes incluyen la API con los mismos límites que el panel. Si superas un límite al añadir o reactivar algo, recibes 402 con el código plan_limit y un mensaje que indica qué límite es.
| Límite | Significado |
|---|---|
| URL monitorizadas | Cada dirección de página de producto que 4pricing lee cuenta por separado: tu propia página y la de cada competidor. Los precios que envías con /own-products no cuentan. |
| Tiendas de la competencia | El número de sitios web de competidores distintos (dominios) que monitorizas. |
| Intervalo mínimo de comprobación | Con qué frecuencia se puede comprobar la misma URL. Los intervalos más cortos se elevan automáticamente al mínimo de tu plan; actualizar una URL demasiado pronto recibe 402 plan_limit. |
Qué llamadas consumen créditos
Leer la página de una tienda consume créditos del monedero de tu cuenta. Las llamadas siguientes comprueban el saldo ANTES de leer nada: si no es suficiente recibes 402 insufficient_credits, y si el monedero no se puede consultar en este momento recibes 503 credit_wallet_unavailable (reintenta más tarde). En ninguno de los dos casos se lee nada ni se cobra nada.
Llamadas que consumen créditos
| Método | Endpoint | Qué hace | Coste |
|---|---|---|---|
| POST | /groups/{group_id}/members | Añade una URL al grupo, por product_id o a partir de una dirección nueva (una dirección nueva se lee de inmediato) | Créditos |
| POST | /groups/{group_id}/refresh | Comprueba ahora todas las URL del grupo | Créditos |
| POST | /groups/{group_id}/suggestions | “Buscar competidores”: URL de competidores propuestas, con una puntuación y el motivo | Créditos |
| POST | /products/ | Empieza a monitorizar una URL (url, name, role own o competitor); se lee de inmediato | Créditos |
| POST | /products/{product_id}/refresh | Comprueba ahora el precio de esta URL (respetando el intervalo mínimo de tu plan) | Créditos |
| POST | /search/ | Busca el precio en vivo de un producto en las tiendas (créditos por cada tienda consultada) | Créditos |
Llamadas gratuitas
- Cualquier lectura (GET): productos, grupos, comparaciones, histórico, alertas, insights, briefings, créditos, estadísticas.
- POST /own-products/ y POST /own-products/bulk: tus propios precios.
- POST /sales/bulk: tus ventas.
- Alertas, ajustes, confirmaciones y todo lo demás que no lea la página de una tienda.
- La monitorización programada de las URL que sigues también consume créditos, en cada comprobación; GET /credits/ muestra tu saldo y la previsión.
- POST /products/import añade muchas URL de una vez sin leerlas de inmediato: la monitorización programada las comprueba, y las cobra, más adelante.
Referencia de endpoints
Todas las operaciones a las que puede acceder una clave, agrupadas según su finalidad. La especificación OpenAPI describe cada una campo por campo, con los cuerpos exactos de petición y respuesta.
Tus propios precios
Dile a 4pricing tu precio directamente, sin leer la página de tu tienda. Gratis.
| Método | Endpoint | Qué hace | Coste |
|---|---|---|---|
| POST | /own-products/ | Envía tu precio actual de un producto (se busca por sku y, si no, por nombre; se crea si no existe) | Gratis |
| POST | /own-products/bulk | Envía tus precios de hasta 2.000 productos en una sola petición; las filas incorrectas se notifican y el resto se guarda | Gratis |
Productos comparados (grupos)
Un grupo es un producto tuyo junto con las páginas de la competencia que venden lo mismo. Contiene el veredicto y el precio recomendado.
| Método | Endpoint | Qué hace | Coste |
|---|---|---|---|
| GET | /groups/ | Todos los productos comparados, cada uno con tu precio, el precio más bajo de la competencia, el veredicto y el precio recomendado | Gratis |
| POST | /groups/ | Crea un producto comparado (nombre, sku, tu precio, coste, estrategia de precios) | Gratis |
| GET | /groups/{group_id} | Un producto comparado | Gratis |
| PUT | /groups/{group_id} | Actualiza el precio, el coste, la estrategia, el intervalo de comprobación o el enlace con la tienda (store_kind) | Gratis |
| DELETE | /groups/{group_id} | Elimina el grupo; sus URL siguen monitorizadas | Gratis |
| GET | /groups/{group_id}/comparison | Tu posición frente a cada competidor, con el precio recomendado y cómo se calculó | Gratis |
| GET | /groups/{group_id}/history | Histórico de precios de cada URL del grupo | Gratis |
| GET | /groups/series | Tu posición en el mercado, día a día, en todos los productos | Gratis |
| POST | /groups/{group_id}/members | Añade una URL al grupo, por product_id o a partir de una dirección nueva (una dirección nueva se lee de inmediato) | Créditos |
| DELETE | /groups/{group_id}/members/{product_id} | Saca una URL del grupo (sigue monitorizada) | Gratis |
| POST | /groups/{group_id}/refresh | Comprueba ahora todas las URL del grupo | Créditos |
| POST | /groups/{group_id}/apply-recommended | Convierte el precio recomendado en tu propio precio en 4pricing (envía el precio que viste; 409 si ha cambiado) | Gratis |
| GET | /groups/suggestions/sources | Fuentes disponibles para “Buscar competidores” | Gratis |
| POST | /groups/{group_id}/suggestions | “Buscar competidores”: URL de competidores propuestas, con una puntuación y el motivo | Créditos |
URL monitorizadas
Una fila por cada dirección de página que 4pricing lee, tuya o de un competidor (role own o competitor).
| Método | Endpoint | Qué hace | Coste |
|---|---|---|---|
| GET | /products/ | URL monitorizadas, paginadas (skip, limit hasta 500; total en X-Total-Count) | Gratis |
| POST | /products/ | Empieza a monitorizar una URL (url, name, role own o competitor); se lee de inmediato | Créditos |
| POST | /products/import | Añade muchas URL de una vez; la monitorización programada las lee más tarde | Gratis |
| GET | /products/export | Exportación CSV de las URL activas | Gratis |
| GET | /products/{product_id} | Una URL monitorizada con su último precio | Gratis |
| PATCH | /products/{product_id} | Cambia el nombre, la categoría o el intervalo de comprobación | Gratis |
| DELETE | /products/{product_id} | Deja de monitorizar una URL (se conserva como inactiva) | Gratis |
| POST | /products/{product_id}/refresh | Comprueba ahora el precio de esta URL (respetando el intervalo mínimo de tu plan) | Créditos |
| GET | /products/{product_id}/history | Histórico de precios de una URL | Gratis |
| GET | /products/sparklines | Un histórico de precios breve de varias URL a la vez | Gratis |
| GET | /products/price-changes | URL cuyo precio cambió en la última comprobación | Gratis |
Búsqueda en vivo
Consulta ahora mismo el precio de un producto en las tiendas.
| Método | Endpoint | Qué hace | Coste |
|---|---|---|---|
| POST | /search/ | Busca el precio en vivo de un producto en las tiendas (créditos por cada tienda consultada) | Créditos |
Alertas
Reglas de precio y la bandeja de las alertas que han saltado.
| Método | Endpoint | Qué hace | Coste |
|---|---|---|---|
| GET | /alerts/ | Tus alertas de precio | Gratis |
| POST | /alerts/ | Crea una alerta (precio por debajo/por encima, bajada en %, de nuevo en stock, rebajado por un competidor…) | Gratis |
| DELETE | /alerts/{alert_id} | Elimina una alerta | Gratis |
| POST | /alerts/{alert_id}/rearm | Vuelve a armar una alerta que ha saltado | Gratis |
| GET | /alerts/events | Bandeja de las alertas que han saltado | Gratis |
| GET | /alerts/events/unread-count | Cuántas alertas sin leer tienes | Gratis |
| POST | /alerts/events/read-all | Marca las alertas como leídas (opcionalmente solo las anteriores a un momento dado) | Gratis |
| POST | /alerts/events/{event_id}/read | Marca una alerta como leída | Gratis |
Créditos
Saldo, previsión y cada movimiento de créditos.
| Método | Endpoint | Qué hace | Coste |
|---|---|---|---|
| GET | /credits/ | Saldo de créditos y previsión de consumo | Gratis |
| GET | /credits/ledger | Cada movimiento de créditos | Gratis |
| GET | /credits/by-product | Créditos usados por URL (total y últimos 30 días) | Gratis |
Tus ventas
Ventas diarias por producto, usadas para calcular el valor de cada oportunidad. Gratis.
| Método | Endpoint | Qué hace | Coste |
|---|---|---|---|
| POST | /sales/bulk | Ventas diarias por producto (sku o group_id, day, units_sold, revenue_cents; opcionales stock_units, cost_cents), hasta 2.000 filas; si vuelves a enviar el mismo producto y día, se sobrescribe | Gratis |
Insights, oportunidades y briefings
Qué ha cambiado, por qué importa y qué hacer, además del briefing diario.
| Método | Endpoint | Qué hace | Coste |
|---|---|---|---|
| GET | /insights/ | Insights con pruebas y propuestas de precio (X-Total-Count) | Gratis |
| GET | /insights/{insight_id} | Un insight en detalle | Gratis |
| POST | /insights/{insight_id}/dismiss | Descarta un insight | Gratis |
| GET | /opportunities/ | Resumen de oportunidades: valor calculado a partir de tus ventas, separado de la estimación de mercado | Gratis |
| GET | /briefings/ | Histórico de briefings diarios | Gratis |
| GET | /briefings/latest | El último briefing diario | Gratis |
| POST | /briefings/generate | Reconstruye el briefing de hoy a partir de los insights abiertos (no envía nada) | Gratis |
Acciones de precio
Cambios de precio que se han propuesto, enviado, aplicado o revertido. Con la API puedes leerlos y confirmar los recibidos por webhook.
| Método | Endpoint | Qué hace | Coste |
|---|---|---|---|
| GET | /actions/ | Acciones de precio: propuestas, enviadas, aplicadas, revertidas | Gratis |
| POST | /actions/{action_id}/confirm | Confirma que tu sistema aplicó un precio recibido mediante price.apply o price.revert | Gratis |
Ajustes
Ajustes de la cuenta, incluida la dirección y el secreto del webhook.
| Método | Endpoint | Qué hace | Coste |
|---|---|---|---|
| GET | /settings/ | Ajustes de la cuenta (tiendas, estrategia de repricing, envío de alertas, webhook) | Gratis |
| PUT | /settings/ | Cambia los ajustes, incluidos alert_webhook_url, alert_webhook_secret y alert_webhook_enabled | Gratis |
Estadísticas
Resumen de la cuenta y estado de las comprobaciones de precio.
| Método | Endpoint | Qué hace | Coste |
|---|---|---|---|
| GET | /stats/ | Estadísticas de la cuenta: URL, alertas, cambios en 24 horas, grupos | Gratis |
| GET | /stats/scrapers | Estado de las comprobaciones de precio por tienda | Gratis |
53 endpoints · https://4pricing.net/api/v1/pricing/openapi.json
Lo que la API no puede hacer
Algunas cosas se quedan en el panel a propósito, donde decide una persona con sesión iniciada. A través de la API estas rutas responden 404:
| Ruta | Por qué |
|---|---|
| /keys/… | Crear y revocar claves: una clave no puede crear más claves. |
| /autopilot/… | La política del piloto automático, que decide qué puede cambiar sin intervención de una persona; una clave filtrada no debe poder ampliarla. |
| /watchlists/… | Las listas de seguimiento, que consumen créditos en cada ejecución. |
| /actions/{action_id}/approve · reject · revert | Aprobar, rechazar o revertir un cambio de precio es una decisión que toma una persona en el panel. La API solo puede leer las acciones y confirmarlas. |
| /insights/{insight_id}/act · /insights/run | Actuar sobre un insight (escribe precios) y recalcular los insights. |
| /admin/… · /internal/… | Administración interna de la plataforma. |
Webhooks
4pricing puede enviar eventos a una dirección tuya (un webhook): hace un POST con JSON cada vez que ocurre algo, así tu sistema no tiene que estar preguntando continuamente.
Hay una dirección de webhook por cuenta; todos los tipos de evento van a ella. Distínguelos por el campo event: la alerta es el único evento que no lo lleva.
Cómo activarlo
- Define alert_webhook_url (una dirección http/https pública en el puerto 80 o 443), alert_webhook_secret y alert_webhook_enabled: true, en el panel o con PUT /settings/.
- Una dirección que resuelva a una red privada o interna se rechaza con 422. Las redirecciones no se siguen.
- Para briefing.ready activa también briefing_webhook_enabled. price.apply y price.revert solo se envían para los grupos enlazados con tu tienda con store_kind = webhook, y solo si hay un secreto definido.
bash
curl -X PUT https://4pricing.net/api/v1/pricing/settings/ \
-H "X-API-Key: PEGA_AQUI_TU_CLAVE" \
-H "Content-Type: application/json" \
-d '{"alert_webhook_url":"https://your-shop.example/4pricing-webhook.php","alert_webhook_secret":"TU_SECRETO_DE_WEBHOOK","alert_webhook_enabled":true,"briefing_webhook_enabled":true}'Eventos
| Evento | Significado |
|---|---|
| alert | Ha saltado una alerta de precio. Cuerpo: event_id (desduplica por él), alert_id, alert_type, message, message_code, message_params, price_cents, currency, triggered_at, product, group, panel_url. Sin campo event. |
| price.apply | Un precio aprobado para aplicar en tu tienda: action_id, group (id, name, sku, ean, store_product_id), old_price_cents, new_price_cents, currency, reason_code, mode y confirm.path. |
| price.revert | Volver al precio anterior. Mismo cuerpo que price.apply; se aplica y se confirma de la misma manera. |
| briefing.ready | El briefing diario está listo: day, headline, summary, sections, total_value_cents (solo a partir de tus ventas), currency, panel_url. |
Cabeceras de cada petición de webhook
| Cabecera | Significado |
|---|---|
| X-4Pricing-Timestamp | Cuándo se envió la petición, en segundos Unix. Rechaza las peticiones demasiado antiguas (por ejemplo, de más de 5 minutos). |
| X-4Pricing-Signature | sha256= seguido del HMAC-SHA256 en hexadecimal del texto “<timestamp>.<raw body>”, calculado con tu secreto de webhook. Presente cuando hay un secreto definido (siempre en price.apply y price.revert). |
| Content-Type | application/json; charset=utf-8 |
| User-Agent | 4pricing-alerts/1.0 |
Verificar la firma
Recalcula la firma a partir de la cabecera de marca de tiempo y del cuerpo en bruto (RAW) que recibiste, compárala en tiempo constante y comprueba la marca de tiempo. Solo entonces procesa el evento. Responde con cualquier 2xx cuando lo hayas aceptado.
php
<?php
$secret = 'TU_SECRETO_DE_WEBHOOK';
// Usa el cuerpo en bruto (RAW), exactamente como lo recibes, no un JSON recodificado
$body = file_get_contents('php://input');
$timestamp = isset($_SERVER['HTTP_X_4PRICING_TIMESTAMP']) ? $_SERVER['HTTP_X_4PRICING_TIMESTAMP'] : '';
$signature = isset($_SERVER['HTTP_X_4PRICING_SIGNATURE']) ? $_SERVER['HTTP_X_4PRICING_SIGNATURE'] : '';
$expected = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $body, $secret);
// Firma incorrecta o con más de 5 minutos de antigüedad: rechazar
if (!hash_equals($expected, $signature) || abs(time() - (int) $timestamp) > 300) {
http_response_code(401);
exit;
}
$event = json_decode($body, true);
if (isset($event['event']) && ($event['event'] === 'price.apply' || $event['event'] === 'price.revert')) {
// Aplica new_price_cents al producto de tu tienda (identifícalo por sku o store_product_id)
// $event['group']['sku'], $event['new_price_cents'], $event['currency']
// Después confirma: POST /actions/{action_id}/confirm con tu clave de API
}
http_response_code(200);
echo 'ok';
javascript
// Node.js, no package needed
const http = require("http");
const crypto = require("crypto");
const SECRET = process.env.WEBHOOK_SECRET; // TU_SECRETO_DE_WEBHOOK
http.createServer((req, res) => {
const chunks = [];
req.on("data", (c) => chunks.push(c));
req.on("end", () => {
// Usa el cuerpo en bruto (RAW), exactamente como lo recibes, no un JSON recodificado
const body = Buffer.concat(chunks);
const ts = String(req.headers["x-4pricing-timestamp"] || "");
const sig = String(req.headers["x-4pricing-signature"] || "");
const expected = "sha256=" + crypto.createHmac("sha256", SECRET).update(ts + ".").update(body).digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(ts)) <= 300;
const same = sig.length === expected.length && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
if (!same || !fresh) {
// Firma incorrecta o con más de 5 minutos de antigüedad: rechazar
res.writeHead(401);
return res.end();
}
const event = JSON.parse(body.toString("utf8"));
if (event.event === "price.apply" || event.event === "price.revert") {
// Aplica new_price_cents al producto de tu tienda (identifícalo por sku o store_product_id)
// Después confirma: POST /actions/{action_id}/confirm con tu clave de API
}
res.writeHead(200);
res.end("ok");
});
}).listen(3000);
python
# Python 3, standard library only
import hashlib
import hmac
import time
SECRET = "TU_SECRETO_DE_WEBHOOK"
def is_valid(raw_body: bytes, timestamp: str, signature: str) -> bool:
# Usa el cuerpo en bruto (RAW), exactamente como lo recibes, no un JSON recodificado
if not timestamp.isdigit() or abs(time.time() - int(timestamp)) > 300:
return False
expected = "sha256=" + hmac.new(
SECRET.encode(), timestamp.encode() + b"." + raw_body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
Entrega y reintentos
- Alertas: ante un error de conexión o de DNS, 429 o 5xx, la petición se reintenta con espera exponencial, con un máximo de 5 intentos. Cualquier otro 4xx es definitivo.
- Si la petición salió pero no llegó respuesta en 10 segundos, no se vuelve a enviar (para evitar un duplicado); aun así, en raras ocasiones puedes recibir un duplicado, por eso desduplica por event_id.
- price.apply y price.revert se envían una sola vez: si la entrega falla, la acción pasa a failed. Desduplica por action_id.
- briefing.ready se envía una vez al día.
Confirmar un precio que has aplicado
- Un price.apply no se considera aplicado hasta que tu sistema lo indique. Después de cambiar el precio en tu tienda, llama a POST /actions/{action_id}/confirm con tu clave de API: la acción pasa de “enviada” a “aplicada”.
- Confirmar dos veces no hace daño: una acción que ya está aplicada se devuelve tal cual.
bash
curl -X POST https://4pricing.net/api/v1/pricing/actions/ACTION_ID/confirm \
-H "X-API-Key: PEGA_AQUI_TU_CLAVE"Cuerpos de ejemplo
json · price.apply
{
"event": "price.apply",
"action_id": "8d0f2c4e-0b7a-4f7e-9d61-3c2b1a0e9f55",
"tenant_id": "…",
"group": {
"id": "2fa912df-440c-4ddf-896a-5d558d56085f",
"name": "Canon RF 50mm F1.8 STM",
"sku": "4515C005AA",
"ean": "4549292181623",
"store_product_id": "1042"
},
"old_price_cents": 124900,
"new_price_cents": 118699,
"currency": "RON",
"reason_code": "undercut",
"mode": "manual",
"confirm": { "method": "POST", "path": "/api/v1/pricing/actions/8d0f2c4e-0b7a-4f7e-9d61-3c2b1a0e9f55/confirm" }
}json · alert
{
"event_id": 18342,
"alert_id": "c1b5…",
"alert_type": "competitor_undercut",
"tenant_id": "…",
"message_code": "competitor_undercut",
"message": "…",
"message_params": { "…": "…" },
"price_cents": 118990,
"currency": "RON",
"triggered_at": "2026-10-13T07:05:12",
"product": { "id": "…", "name": "…", "url": "https://…", "retailer": "…", "current_price_cents": 118990, "in_stock": true },
"group": { "id": "…", "name": "Canon RF 50mm F1.8 STM", "sku": "4515C005AA" },
"panel_url": "https://panel.4pricing.net/…"
}Errores
Los errores llegan con el estado HTTP adecuado y un cuerpo JSON {"detail": …}. detail es un texto, un objeto con un código que puedes tratar en tu programa o, en el caso de 422, la lista de campos no válidos.
| Estado | Código | Significado |
|---|---|---|
| 401 | — | La clave de API falta, es incorrecta o ha sido revocada. |
| 402 | plan_required | La cuenta no tiene una suscripción activa a 4pricing. |
| 402 | plan_limit | Se superaría un límite del plan (URL monitorizadas, tiendas de la competencia) o se pidió una actualización antes del intervalo mínimo. El mensaje indica cuál. |
| 402 | insufficient_credits | No hay créditos suficientes para una llamada que lee la página de una tienda. No se leyó ni se cobró nada. |
| 403 | — | Bloqueado por el cortafuegos antes de llegar a la API; normalmente por un User-Agent genérico como Python-urllib. Envía tu propio User-Agent. |
| 404 | — | El recurso no existe, no pertenece a tu cuenta o la ruta no está disponible a través de la API. |
| 409 | recommendation_moved · own_from_listing · invalid_state · webhook_secret_required | Conflicto: el precio recomendado cambió (recommendation_moved), la acción está en otro estado (invalid_state), se requiere un secreto de webhook o la operación no se aplica a este elemento. |
| 422 | — | Datos no válidos, por ejemplo una URL sin http/https o un campo obligatorio ausente. detail enumera los campos. |
| 429 | — | Más de 60 peticiones por minuto para la cuenta (o demasiadas claves no válidas). Espera los segundos indicados en Retry-After. |
| 503 | credit_wallet_unavailable | El monedero de créditos no se puede consultar en este momento. Reintenta más tarde; no se cobró nada. |
Cuerpos de error
json
{"detail": "Invalid or missing API key"}
{"detail": {"code": "plan_required", "message": "…"}}
{"detail": {"code": "insufficient_credits", "message": "…"}}
{"detail": [{"loc": ["body", "price_cents"], "msg": "…", "type": "…"}]}Ejemplos de código
La misma lectura en cuatro lenguajes, con pausa y reintento ante 429, y después el envío de ventas. Sustituye PEGA_AQUI_TU_CLAVE por tu clave (o define la variable de entorno PRICING_API_KEY).
Node.js: lee tus productos comparados
javascript
// Node.js 18+ (fetch is built in, no package needed)
const API_KEY = process.env.PRICING_API_KEY; // PEGA_AQUI_TU_CLAVE
const BASE = "https://4pricing.net/api/v1/pricing";
async function call(path, options = {}) {
for (let attempt = 0; attempt < 3; attempt++) {
const res = await fetch(BASE + path, {
...options,
headers: { "X-API-Key": API_KEY, "Content-Type": "application/json", ...(options.headers || {}) },
});
if (res.status === 429) {
// Más de 60 peticiones/minuto: espera los segundos indicados en Retry-After y vuelve a intentarlo
const wait = Number(res.headers.get("Retry-After") || 60);
await new Promise((r) => setTimeout(r, wait * 1000));
continue;
}
if (!res.ok) throw new Error(res.status + " " + (await res.text()));
return res.status === 204 ? null : res.json();
}
throw new Error("rate limited");
}
async function main() {
// Tus productos comparados: tu precio, el competidor más barato, el veredicto
const groups = await call("/groups/");
for (const g of groups) {
console.log(g.name, g.own_price_cents_effective, g.min_competitor_cents, g.verdict);
}
}
main().catch((err) => {
console.error(err.message);
process.exit(1);
});
Python: lee tus productos comparados
python
# Python 3, standard library only
import json
import os
import time
import urllib.error
import urllib.request
API_KEY = os.environ["PRICING_API_KEY"] # PEGA_AQUI_TU_CLAVE
BASE = "https://4pricing.net/api/v1/pricing"
def call(path, method="GET", payload=None):
data = json.dumps(payload).encode() if payload is not None else None
for _ in range(3):
req = urllib.request.Request(BASE + path, data=data, method=method, headers={
"X-API-Key": API_KEY, "Content-Type": "application/json", "Accept": "application/json",
# Define tu propio User-Agent: el que traen por defecto algunas librerías HTTP (Python urllib) se bloquea con 403
"User-Agent": "my-shop-integration/1.0",
})
try:
with urllib.request.urlopen(req, timeout=30) as res:
body = res.read()
return json.loads(body) if body else None
except urllib.error.HTTPError as err:
if err.code == 429:
# Más de 60 peticiones/minuto: espera los segundos indicados en Retry-After y vuelve a intentarlo
time.sleep(int(err.headers.get("Retry-After", "60")))
continue
raise
raise RuntimeError("rate limited")
# Tus productos comparados: tu precio, el competidor más barato, el veredicto
for g in call("/groups/"):
print(g["name"], g["own_price_cents_effective"], g["min_competitor_cents"], g["verdict"])
curl: envía tus ventas diarias
bash
curl -X POST https://4pricing.net/api/v1/pricing/sales/bulk \
-H "X-API-Key: PEGA_AQUI_TU_CLAVE" \
-H "Content-Type: application/json" \
-d '{"rows":[{"sku":"CAMISETA-BLANCA-M","day":"2026-10-12","units_sold":7,"revenue_cents":34993}]}'PHP: envía tus ventas diarias
php
<?php
$apiKey = 'PEGA_AQUI_TU_CLAVE';
// Ventas diarias por producto (por sku o group_id), hasta 2.000 filas por petición
$rows = [
['sku' => 'CAMISETA-BLANCA-M', 'day' => '2026-10-12', 'units_sold' => 7, 'revenue_cents' => 34993],
];
$ch = curl_init('https://4pricing.net/api/v1/pricing/sales/bulk');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_USERAGENT => 'my-shop-integration/1.0',
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['X-API-Key: ' . $apiKey, 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode(['rows' => $rows]),
]);
$body = curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_HTTP_CODE) . ' ' . $body; // 200 {"upserted": 1, "skipped": []}
Preguntas frecuentes
¿Tengo que ser programador para usar la API de 4pricing?
No. La guía rápida te da un archivo PHP ya preparado: creas una clave en el panel, la pegas en el archivo, subes el archivo a tu hosting y lo abres en el navegador. Para integraciones más profundas (un ERP, una plataforma de tiendas), un desarrollador puede usar la referencia de endpoints y la especificación OpenAPI.
¿Cuánto cuesta la API de 4pricing?
La API está incluida en todas las suscripciones de 4pricing, con los mismos límites que el panel. Leer tus datos es gratis; solo las llamadas que leen la página de una tienda (añadir una URL nueva, actualizar, búsqueda en vivo, buscar competidores) consumen créditos, igual que en el panel.
¿Qué llamadas consumen créditos?
POST /products/, POST /products/{product_id}/refresh, POST /groups/{group_id}/refresh, POST /groups/{group_id}/members con una URL nueva, POST /groups/{group_id}/suggestions y POST /search/. Si el saldo no es suficiente recibes 402 insufficient_credits y no se lee nada.
¿Los precios que envío por la API cuentan para el límite de URL de mi plan?
No. Solo cuentan las direcciones que 4pricing lee de una página: tus propias páginas de producto y cada página de la competencia por separado. Los precios enviados con POST /own-products/ o /own-products/bulk no cuentan ni como URL ni como créditos.
¿Puedo limitar a qué puede acceder una clave?
No, no hay scopes: una clave ve y puede modificar toda tu cuenta de 4pricing. Usa una clave distinta para cada integración y revoca las que ya no uses; la revocación surte efecto de inmediato.
¿Qué pasa si envío más de 60 peticiones por minuto?
Recibes 429 con una cabecera Retry-After. El límite es por cuenta y lo comparten todas tus claves. Espera el número de segundos indicado en Retry-After y vuelve a intentarlo; para muchos productos usa las llamadas bulk (hasta 2.000 filas por petición).
¿Cómo hago que el precio recomendado llegue automáticamente a mi tienda?
Enlaza el grupo con tu tienda con store_kind = webhook, define una dirección y un secreto de webhook y gestiona el evento price.apply: cambia el precio en tu tienda y después llama a POST /actions/{action_id}/confirm. Aprobar un cambio de precio sigue siendo una decisión que se toma en el panel.
¿Hay un SDK o un plugin ya preparado?
No hay un SDK ni un plugin oficial. La API es REST puro con JSON, así que sirve cualquier lenguaje capaz de hacer peticiones HTTPS; la especificación OpenAPI se puede cargar en herramientas que generan un cliente automáticamente.