Documentación para desarrolladores
API REST de RM
Conecta tu ERP, tu punto de venta o tu propio sistema con una tienda de RM: catálogo, precios, existencias, clientes y pedidos, con webhooks para enterarte al instante.
Introducción
La API es REST sobre HTTPS, con JSON en UTF-8. Cada tienda tiene sus propias llaves y una llave solo ve y modifica los datos de su tienda. Hay 111 operaciones agrupadas por área: catálogo, inventario, precios, clientes, pedidos e integraciones.
Base de la API ({base_url} en esta documentación; incluye /api/v1):
- Servidor público:
https://retail-s.online/api/v1 - Desarrollo local:
http://127.0.0.1:8000/api/v1
Para probar que todo funciona:
export RM_BASE_URL="https://retail-s.online/api/v1"
export RM_API_KEY="rk_..." # tu llave; nunca la subas a un repositorio
curl -s "$RM_BASE_URL/" -H "Authorization: Bearer $RM_API_KEY"
Responde la tienda, el nombre de la llave y sus permisos:
{
"api_version": "1",
"tenant": {"name": "Mi tienda", "slug": "mia", "default_currency": "USD", "prices_include_tax": false},
"api_key": "Conector ERP",
"scopes": ["read_products", "write_products", "read_inventory"]
}
Los entornos locales con certificado propio (por ejemplo el servidor de pruebas en la red local) necesitan que el cliente confíe en esa CA: en curl, --cacert ca.crt. El servidor público usa un certificado normal.
Autenticación y scopes
Genera una llave en el back office (Configuración → Llaves de API) y envíala en cada petición:
Authorization: Bearer rk_1a2b3c4d_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
- Sin llave, o con una llave inválida o desactivada:
401. La sesión del back office no sirve para la API. - La llave se muestra una sola vez al crearla. Guárdala en una variable de entorno o en un gestor de secretos.
Cada llave tiene permisos por área, elegidos al crearla: sin acceso, leer, o leer y escribir. Un GET pide read_<área>; POST, PUT, PATCH y DELETE piden write_<área>. Escribir incluye leer. Sin el permiso: 403.
| Área | Scopes | Recursos |
|---|---|---|
| Catálogo | read_products write_products | productos, variantes, imágenes, categorías, marcas, colecciones, atributos, plantillas, modificadores, traducciones, alta masiva e importación |
| Inventario | read_inventory write_inventory | existencias, ubicaciones |
| Precios | read_prices write_prices | precios, listas de precios |
| Clientes | read_customers write_customers | clientes, grupos de clientes |
| Pedidos | read_orders write_orders | pedidos, cambios de estado, pagos, devoluciones, reembolsos, paqueterías, medios de pago |
| Integraciones | read_integrations write_integrations | conexiones, corridas de sincronización, referencias externas |
Da a cada conector solo los permisos que usa (por ejemplo, read_orders sin write_orders si solo lee pedidos).
Convenciones
- Identidad. Categorías, marcas, colecciones, atributos, ubicaciones, listas de precios, grupos y productos se identifican por código (como en BC), guardado en mayúsculas:
ply-001yPLY-001son lo mismo. Las variantes, por SKU. Clientes, pedidos, líneas, devoluciones, imágenes, conexiones y referencias externas, por suidpúblico: un UUID que debes guardar como texto. La API nunca expone IDs internos. - Upsert.
PUT /<recurso>/<código>/crea (201) o reemplaza (200).PATCHactualiza solo los campos enviados y da404si no existe. - Declarativo. Las listas
options,variants(convariants_mode: "exact"),addresses,attributes,modifiersyhoursdejan el registro exactamente como las envías. - Decimales como texto (
"199.0000") para no perder precisión. Fechas en ISO 8601. - Paginación por cursor.
?limit=de 1 a 500 (100 por omisión). La respuesta es{"next": url, "results": [...]}; pide la URL denexttal cual hasta que seanull. Los registros salen en orden de alta, así que nada se repite ni se salta mientras lees. No haycountniprevious, yoffsetresponde400. - Sincronización incremental.
?updated_since=2026-10-14T00:00:00Zen los listados; en pedidos también?placed_since=. - Reintentos seguros.
POST /orders/,/pay/,/returns/y/refunds/aceptan la cabeceraIdempotency-Key(hasta 100 caracteres). Misma llave y mismos datos: no se repite la operación. Misma llave con otros datos:409.
Las reglas de negocio de cada recurso (variantes exactas, horarios, inventario con ERP, reembolsos…) están en la referencia completa.
Recursos
Cada operación trae su permiso y un ejemplo con curl (usa las variables del ejemplo de la introducción). Abre una para verlo. Los parámetros, cuerpos y respuestas completos están en el OpenAPI.
Conexión
Probar la llave y ver sus permisos.
GET / Probar conexión
Devuelve la tienda, el nombre de la llave y sus permisos. Sirve cualquier llave válida, sin importar sus scopes.
curl -s -X GET "$RM_BASE_URL/" \
-H "Authorization: Bearer $RM_API_KEY"
Catálogo
Productos, variantes, imágenes, categorías, marcas, colecciones, atributos, plantillas, modificadores y traducciones. Permiso `products`.
GET /categories/ Listar categories read_products
Permiso: `read_products`.
curl -s -X GET "$RM_BASE_URL/categories/" \
-H "Authorization: Bearer $RM_API_KEY"
POST /categories/ Crear la categoría write_products
El cuerpo lleva `code`. `409`/`400` si el código ya existe. Permiso: `write_products`.
curl -s -X POST "$RM_BASE_URL/categories/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"code": "EJEMPLO",
"name": "Playeras",
"parent": "ROPA",
"is_active": true
}'
GET /categories/{code}/ Obtener la categoría read_products
Permiso: `read_products`.
curl -s -X GET "$RM_BASE_URL/categories/{code}/" \
-H "Authorization: Bearer $RM_API_KEY"
PUT /categories/{code}/ Crear o reemplazar la categoría (upsert) write_products
`201` si lo crea, `200` si lo reemplaza. El código de la ruta manda sobre el del cuerpo. Permiso: `write_products`.
curl -s -X PUT "$RM_BASE_URL/categories/{code}/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Playeras",
"parent": "ROPA",
"is_active": true
}'
PATCH /categories/{code}/ Actualizar campos de la categoría write_products
Solo los campos enviados; `404` si no existe. Permiso: `write_products`.
curl -s -X PATCH "$RM_BASE_URL/categories/{code}/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Playeras"
}'
DELETE /categories/{code}/ Eliminar la categoría write_products
`400` si otros registros dependen de este. Permiso: `write_products`.
curl -s -X DELETE "$RM_BASE_URL/categories/{code}/" \
-H "Authorization: Bearer $RM_API_KEY"
GET /variant-templates/ Listar variant-templates read_products
Permiso: `read_products`.
curl -s -X GET "$RM_BASE_URL/variant-templates/" \
-H "Authorization: Bearer $RM_API_KEY"
POST /variant-templates/ Crear la plantilla de variantes write_products
El cuerpo lleva `code`. `409`/`400` si el código ya existe. Permiso: `write_products`.
curl -s -X POST "$RM_BASE_URL/variant-templates/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"code": "EJEMPLO",
"name": "Ropa",
"options": [
{
"name": "Talla",
"values": [
"CH",
"M",
"G"
]
}
]
}'
GET /variant-templates/{code}/ Obtener la plantilla de variantes read_products
Permiso: `read_products`.
curl -s -X GET "$RM_BASE_URL/variant-templates/{code}/" \
-H "Authorization: Bearer $RM_API_KEY"
PUT /variant-templates/{code}/ Crear o reemplazar la plantilla de variantes (upsert) write_products
`201` si lo crea, `200` si lo reemplaza. El código de la ruta manda sobre el del cuerpo. Permiso: `write_products`.
curl -s -X PUT "$RM_BASE_URL/variant-templates/{code}/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Ropa",
"options": [
{
"name": "Talla",
"values": [
"CH",
"M",
"G"
]
}
]
}'
PATCH /variant-templates/{code}/ Actualizar campos de la plantilla de variantes write_products
Solo los campos enviados; `404` si no existe. Permiso: `write_products`.
curl -s -X PATCH "$RM_BASE_URL/variant-templates/{code}/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Ropa"
}'
DELETE /variant-templates/{code}/ Eliminar la plantilla de variantes write_products
`400` si otros registros dependen de este. Permiso: `write_products`.
curl -s -X DELETE "$RM_BASE_URL/variant-templates/{code}/" \
-H "Authorization: Bearer $RM_API_KEY"
GET /brands/ Listar brands read_products
Permiso: `read_products`.
curl -s -X GET "$RM_BASE_URL/brands/" \
-H "Authorization: Bearer $RM_API_KEY"
POST /brands/ Crear la marca write_products
El cuerpo lleva `code`. `409`/`400` si el código ya existe. Permiso: `write_products`.
curl -s -X POST "$RM_BASE_URL/brands/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"code": "EJEMPLO",
"name": "Marca Ejemplo",
"is_active": true
}'
GET /brands/{code}/ Obtener la marca read_products
Permiso: `read_products`.
curl -s -X GET "$RM_BASE_URL/brands/{code}/" \
-H "Authorization: Bearer $RM_API_KEY"
PUT /brands/{code}/ Crear o reemplazar la marca (upsert) write_products
`201` si lo crea, `200` si lo reemplaza. El código de la ruta manda sobre el del cuerpo. Permiso: `write_products`.
curl -s -X PUT "$RM_BASE_URL/brands/{code}/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Marca Ejemplo",
"is_active": true
}'
PATCH /brands/{code}/ Actualizar campos de la marca write_products
Solo los campos enviados; `404` si no existe. Permiso: `write_products`.
curl -s -X PATCH "$RM_BASE_URL/brands/{code}/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Marca Ejemplo"
}'
DELETE /brands/{code}/ Eliminar la marca write_products
`400` si otros registros dependen de este. Permiso: `write_products`.
curl -s -X DELETE "$RM_BASE_URL/brands/{code}/" \
-H "Authorization: Bearer $RM_API_KEY"
GET /collections/ Listar collections read_products
Permiso: `read_products`.
curl -s -X GET "$RM_BASE_URL/collections/" \
-H "Authorization: Bearer $RM_API_KEY"
POST /collections/ Crear la colección write_products
El cuerpo lleva `code`. `409`/`400` si el código ya existe. Permiso: `write_products`.
curl -s -X POST "$RM_BASE_URL/collections/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"code": "EJEMPLO",
"name": "Novedades",
"is_active": true,
"featured": false,
"products": [
"PLY-001"
]
}'
GET /collections/{code}/ Obtener la colección read_products
Permiso: `read_products`.
curl -s -X GET "$RM_BASE_URL/collections/{code}/" \
-H "Authorization: Bearer $RM_API_KEY"
PUT /collections/{code}/ Crear o reemplazar la colección (upsert) write_products
`201` si lo crea, `200` si lo reemplaza. El código de la ruta manda sobre el del cuerpo. Permiso: `write_products`.
curl -s -X PUT "$RM_BASE_URL/collections/{code}/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Novedades",
"is_active": true,
"featured": false,
"products": [
"PLY-001"
]
}'
PATCH /collections/{code}/ Actualizar campos de la colección write_products
Solo los campos enviados; `404` si no existe. Permiso: `write_products`.
curl -s -X PATCH "$RM_BASE_URL/collections/{code}/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Novedades"
}'
DELETE /collections/{code}/ Eliminar la colección write_products
`400` si otros registros dependen de este. Permiso: `write_products`.
curl -s -X DELETE "$RM_BASE_URL/collections/{code}/" \
-H "Authorization: Bearer $RM_API_KEY"
GET /modifier-groups/ Listar modifier-groups read_products
Permiso: `read_products`.
curl -s -X GET "$RM_BASE_URL/modifier-groups/" \
-H "Authorization: Bearer $RM_API_KEY"
POST /modifier-groups/ Crear el grupo de modificadores write_products
El cuerpo lleva `code`. `409`/`400` si el código ya existe. Permiso: `write_products`.
curl -s -X POST "$RM_BASE_URL/modifier-groups/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"code": "EJEMPLO",
"name": "Extras",
"min_select": 0,
"max_select": 2,
"modifiers": [
{
"code": "QUESO",
"name": "Queso",
"price": "0.75"
},
{
"code": "TOCINO",
"name": "Tocino",
"price": "1.25"
}
]
}'
GET /modifier-groups/{code}/ Obtener el grupo de modificadores read_products
Permiso: `read_products`.
curl -s -X GET "$RM_BASE_URL/modifier-groups/{code}/" \
-H "Authorization: Bearer $RM_API_KEY"
PUT /modifier-groups/{code}/ Crear o reemplazar el grupo de modificadores (upsert) write_products
`201` si lo crea, `200` si lo reemplaza. El código de la ruta manda sobre el del cuerpo. Permiso: `write_products`.
curl -s -X PUT "$RM_BASE_URL/modifier-groups/{code}/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Extras",
"min_select": 0,
"max_select": 2,
"modifiers": [
{
"code": "QUESO",
"name": "Queso",
"price": "0.75"
},
{
"code": "TOCINO",
"name": "Tocino",
"price": "1.25"
}
]
}'
PATCH /modifier-groups/{code}/ Actualizar campos de el grupo de modificadores write_products
Solo los campos enviados; `404` si no existe. Permiso: `write_products`.
curl -s -X PATCH "$RM_BASE_URL/modifier-groups/{code}/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Extras"
}'
DELETE /modifier-groups/{code}/ Eliminar el grupo de modificadores write_products
`400` si otros registros dependen de este. Permiso: `write_products`.
curl -s -X DELETE "$RM_BASE_URL/modifier-groups/{code}/" \
-H "Authorization: Bearer $RM_API_KEY"
GET /attributes/ Listar attributes read_products
Permiso: `read_products`.
curl -s -X GET "$RM_BASE_URL/attributes/" \
-H "Authorization: Bearer $RM_API_KEY"
POST /attributes/ Crear el atributo write_products
El cuerpo lleva `code`. `409`/`400` si el código ya existe. Permiso: `write_products`.
curl -s -X POST "$RM_BASE_URL/attributes/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"code": "EJEMPLO",
"name": "Material",
"type": "option",
"scope": "product",
"options": [
"Algodón",
"Poliéster"
],
"is_filterable": true
}'
GET /attributes/{code}/ Obtener el atributo read_products
Permiso: `read_products`.
curl -s -X GET "$RM_BASE_URL/attributes/{code}/" \
-H "Authorization: Bearer $RM_API_KEY"
PUT /attributes/{code}/ Crear o reemplazar el atributo (upsert) write_products
`201` si lo crea, `200` si lo reemplaza. El código de la ruta manda sobre el del cuerpo. Permiso: `write_products`.
curl -s -X PUT "$RM_BASE_URL/attributes/{code}/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Material",
"type": "option",
"scope": "product",
"options": [
"Algodón",
"Poliéster"
],
"is_filterable": true
}'
PATCH /attributes/{code}/ Actualizar campos de el atributo write_products
Solo los campos enviados; `404` si no existe. Permiso: `write_products`.
curl -s -X PATCH "$RM_BASE_URL/attributes/{code}/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Material"
}'
DELETE /attributes/{code}/ Eliminar el atributo write_products
`400` si otros registros dependen de este. Permiso: `write_products`.
curl -s -X DELETE "$RM_BASE_URL/attributes/{code}/" \
-H "Authorization: Bearer $RM_API_KEY"
GET /products/ Listar productos read_products
Permiso: `read_products`.
curl -s -X GET "$RM_BASE_URL/products/" \
-H "Authorization: Bearer $RM_API_KEY"
POST /products/ Crear producto write_products
Permiso: `write_products`.
curl -s -X POST "$RM_BASE_URL/products/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"code": "PLY-001",
"name": "Playera básica",
"status": "active",
"category": "PLAYERAS",
"short_description": "Algodón 100 %",
"base_unit": "PZA",
"tax_exempt": false,
"track_inventory": true,
"options": [
{
"name": "Color",
"values": [
"Negro",
"Blanco"
]
},
{
"name": "Talla",
"values": [
"CH",
"M",
"G"
]
}
],
"variants": [
{
"code": "NEGRO-CH",
"sku": "PLY-001-N-CH",
"barcode": "7501234567890"
}
],
"attributes": {
"MATERIAL": "Algodón"
}
}'
GET /products/{code}/ Obtener producto read_products
Incluye variantes con sus opciones, existencias por ubicación y precios, imágenes y atributos. Permiso: `read_products`.
curl -s -X GET "$RM_BASE_URL/products/{code}/" \
-H "Authorization: Bearer $RM_API_KEY"
PUT /products/{code}/ Crear o reemplazar producto (upsert) write_products
`201`/`200`. `options` y `attributes` son declarativos; se aplica completo o nada. Ver `variants_mode: exact` en `docs/api.md`. Permiso: `write_products`.
curl -s -X PUT "$RM_BASE_URL/products/{code}/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Playera básica",
"status": "active",
"category": "PLAYERAS",
"short_description": "Algodón 100 %",
"base_unit": "PZA",
"tax_exempt": false,
"track_inventory": true,
"options": [
{
"name": "Color",
"values": [
"Negro",
"Blanco"
]
},
{
"name": "Talla",
"values": [
"CH",
"M",
"G"
]
}
],
"variants": [
{
"code": "NEGRO-CH",
"sku": "PLY-001-N-CH",
"barcode": "7501234567890"
}
],
"attributes": {
"MATERIAL": "Algodón"
}
}'
PATCH /products/{code}/ Actualizar campos de un producto write_products
Permiso: `write_products`.
curl -s -X PATCH "$RM_BASE_URL/products/{code}/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"status": "archived"
}'
DELETE /products/{code}/ Eliminar producto write_products
Permiso: `write_products`.
curl -s -X DELETE "$RM_BASE_URL/products/{code}/" \
-H "Authorization: Bearer $RM_API_KEY"
POST /products/{code}/duplicate/ Duplicar producto write_products
Permiso: `write_products`.
curl -s -X POST "$RM_BASE_URL/products/{code}/duplicate/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"code": "PLY-001-COPIA",
"name": "Playera básica (copia)",
"copy_images": true,
"copy_prices": true
}'
POST /products/{code}/images/ Agregar o reemplazar imagen write_products
`multipart/form-data` con `image`, o JSON con `url` (https, sin descargarla). Con `external_key` repetida reemplaza la imagen (`200`). Permiso: `write_products`.
curl -s -X POST "$RM_BASE_URL/products/{code}/images/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://cdn.ejemplo.com/playera.jpg",
"external_key": "LS-1",
"alt_text": "Playera negra de frente",
"variant_skus": [
"PLY-001-N-CH"
]
}'
DELETE /products/{code}/images/ Borrar imagen por clave externa write_products
Permiso: `write_products`.
curl -s -X DELETE "$RM_BASE_URL/products/{code}/images/" \
-H "Authorization: Bearer $RM_API_KEY"
DELETE /products/{code}/images/{image_id}/ Borrar imagen por id write_products
Permiso: `write_products`.
curl -s -X DELETE "$RM_BASE_URL/products/{code}/images/{image_id}/" \
-H "Authorization: Bearer $RM_API_KEY"
GET /variants/ Listar variantes read_products
Permiso: `read_products`.
curl -s -X GET "$RM_BASE_URL/variants/" \
-H "Authorization: Bearer $RM_API_KEY"
GET /variants/{sku}/ Obtener variante read_products
Permiso: `read_products`.
curl -s -X GET "$RM_BASE_URL/variants/{sku}/" \
-H "Authorization: Bearer $RM_API_KEY"
PATCH /variants/{sku}/ Actualizar variante write_products
Solo `sku`, `barcode`, `weight`, `is_active` y `attributes`; las combinaciones salen de las opciones del producto. Permiso: `write_products`.
curl -s -X PATCH "$RM_BASE_URL/variants/{sku}/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"barcode": "7501234567890",
"is_active": true
}'
DELETE /variants/{sku}/ Eliminar variante write_products
`409` si tiene pedidos (desactívala con `is_active: false`) o si es la única del producto. Permiso: `write_products`.
curl -s -X DELETE "$RM_BASE_URL/variants/{sku}/" \
-H "Authorization: Bearer $RM_API_KEY"
GET /translations/ Consultar traducciones read_products
Permiso: `read_products`.
curl -s -X GET "$RM_BASE_URL/translations/" \
-H "Authorization: Bearer $RM_API_KEY"
PUT /translations/ Guardar traducciones write_products
El idioma debe estar activo como extra en la tienda. Un valor vacío borra la traducción. Permiso: `write_products`.
curl -s -X PUT "$RM_BASE_URL/translations/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"object_type": "product",
"object_key": "PLY-001",
"language": "en",
"fields": {
"name": "Basic T-shirt"
}
}'
Catálogo masivo
Alta masiva e importación NDJSON de productos. Permiso `products`.
POST /products/bulk/ Alta masiva de productos (hasta 500) write_products
Upsert por código, procesado por el worker. Un producto malo no tumba a los demás. Permiso: `write_products`.
curl -s -X POST "$RM_BASE_URL/products/bulk/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"code": "PLY-001",
"name": "Playera básica"
},
{
"code": "PLY-002",
"name": "Playera estampada"
}
]
}'
GET /products/bulk/{id}/ Estado y resultado de una alta masiva read_products
Permiso: `read_products`.
curl -s -X GET "$RM_BASE_URL/products/bulk/{id}/" \
-H "Authorization: Bearer $RM_API_KEY"
POST /products/import/ Importar catálogo NDJSON (solo crea) write_products
`multipart/form-data` con `file` (NDJSON, hasta 256 MB). Pensado para primeras cargas de decenas o cientos de miles de productos. Permiso: `write_products`.
curl -s -X POST "$RM_BASE_URL/products/import/" \
-H "Authorization: Bearer $RM_API_KEY" \
-F "file=@catalogo.ndjson"
GET /products/import/{id}/ Estado de una importación read_products
Permiso: `read_products`.
curl -s -X GET "$RM_BASE_URL/products/import/{id}/" \
-H "Authorization: Bearer $RM_API_KEY"
GET /products/import/{id}/rows/ Resultado completo de una importación (NDJSON) read_products
Una fila por línea (`application/x-ndjson`), en orden y sin paginar. Permiso: `read_products`.
curl -s -X GET "$RM_BASE_URL/products/import/{id}/rows/" \
-H "Authorization: Bearer $RM_API_KEY"
Inventario
Existencias y ubicaciones. Permiso `inventory`.
GET /locations/ Listar locations read_inventory
Permiso: `read_inventory`.
curl -s -X GET "$RM_BASE_URL/locations/" \
-H "Authorization: Bearer $RM_API_KEY"
POST /locations/ Crear la ubicación write_inventory
El cuerpo lleva `code`. `409`/`400` si el código ya existe. Permiso: `write_inventory`.
curl -s -X POST "$RM_BASE_URL/locations/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"code": "EJEMPLO",
"name": "Centro de distribución",
"kind": "warehouse",
"allows_pickup": false,
"ships_orders": true,
"hours": [
{
"weekday": 0,
"opens": "09:00",
"closes": "18:00"
}
]
}'
GET /locations/{code}/ Obtener la ubicación read_inventory
Permiso: `read_inventory`.
curl -s -X GET "$RM_BASE_URL/locations/{code}/" \
-H "Authorization: Bearer $RM_API_KEY"
PUT /locations/{code}/ Crear o reemplazar la ubicación (upsert) write_inventory
`201` si lo crea, `200` si lo reemplaza. El código de la ruta manda sobre el del cuerpo. Permiso: `write_inventory`.
curl -s -X PUT "$RM_BASE_URL/locations/{code}/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Centro de distribución",
"kind": "warehouse",
"allows_pickup": false,
"ships_orders": true,
"hours": [
{
"weekday": 0,
"opens": "09:00",
"closes": "18:00"
}
]
}'
PATCH /locations/{code}/ Actualizar campos de la ubicación write_inventory
Solo los campos enviados; `404` si no existe. Permiso: `write_inventory`.
curl -s -X PATCH "$RM_BASE_URL/locations/{code}/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Centro de distribución"
}'
DELETE /locations/{code}/ Eliminar la ubicación write_inventory
`400` si otros registros dependen de este. Permiso: `write_inventory`.
curl -s -X DELETE "$RM_BASE_URL/locations/{code}/" \
-H "Authorization: Bearer $RM_API_KEY"
GET /inventory/ Consultar existencias read_inventory
Permiso: `read_inventory`.
curl -s -X GET "$RM_BASE_URL/inventory/" \
-H "Authorization: Bearer $RM_API_KEY"
POST /inventory/bulk/ Fijar existencias en bloque (absolutas) write_inventory
`on_hand` es la existencia absoluta del ERP, no un ajuste. Hasta 1000 filas. Todo o nada, salvo `partial=true`. Permiso: `write_inventory`.
curl -s -X POST "$RM_BASE_URL/inventory/bulk/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"sku": "PLY-001-N-CH",
"location": "CEDIS",
"on_hand": "12"
},
{
"sku": "PLY-001-N-CH",
"location": "TDA-CENTRO",
"on_hand": "3",
"reference": "ERP 2026-10-14"
}
]
}'
Precios
Precios y listas de precios. Permiso `prices`.
GET /price-lists/ Listar price-lists read_prices
Permiso: `read_prices`.
curl -s -X GET "$RM_BASE_URL/price-lists/" \
-H "Authorization: Bearer $RM_API_KEY"
POST /price-lists/ Crear la lista de precios write_prices
El cuerpo lleva `code`. `409`/`400` si el código ya existe. Permiso: `write_prices`.
curl -s -X POST "$RM_BASE_URL/price-lists/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"code": "EJEMPLO",
"name": "Mayoreo",
"currency": "USD",
"prices_include_tax": false,
"priority": 10,
"customer_groups": [
"MAYORISTAS"
]
}'
GET /price-lists/{code}/ Obtener la lista de precios read_prices
Permiso: `read_prices`.
curl -s -X GET "$RM_BASE_URL/price-lists/{code}/" \
-H "Authorization: Bearer $RM_API_KEY"
PUT /price-lists/{code}/ Crear o reemplazar la lista de precios (upsert) write_prices
`201` si lo crea, `200` si lo reemplaza. El código de la ruta manda sobre el del cuerpo. Permiso: `write_prices`.
curl -s -X PUT "$RM_BASE_URL/price-lists/{code}/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Mayoreo",
"currency": "USD",
"prices_include_tax": false,
"priority": 10,
"customer_groups": [
"MAYORISTAS"
]
}'
PATCH /price-lists/{code}/ Actualizar campos de la lista de precios write_prices
Solo los campos enviados; `404` si no existe. Permiso: `write_prices`.
curl -s -X PATCH "$RM_BASE_URL/price-lists/{code}/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Mayoreo"
}'
DELETE /price-lists/{code}/ Eliminar la lista de precios write_prices
`400` si otros registros dependen de este. Permiso: `write_prices`.
curl -s -X DELETE "$RM_BASE_URL/price-lists/{code}/" \
-H "Authorization: Bearer $RM_API_KEY"
GET /prices/ Consultar precios read_prices
Permiso: `read_prices`.
curl -s -X GET "$RM_BASE_URL/prices/" \
-H "Authorization: Bearer $RM_API_KEY"
POST /prices/bulk/ Fijar precios en bloque write_prices
Hasta 1000 filas; `price: null` borra la línea. Todo o nada, salvo `partial=true`. Permiso: `write_prices`.
curl -s -X POST "$RM_BASE_URL/prices/bulk/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"price_list": "GENERAL",
"sku": "PLY-001-N-CH",
"price": "249.00",
"compare_at_price": "299.00"
},
{
"price_list": "GENERAL",
"sku": "PLY-001-N-CH",
"price": "220.00",
"min_quantity": "6"
},
{
"price_list": "MAYOREO",
"sku": "PLY-001-N-M",
"price": null
}
]
}'
Clientes
Clientes y grupos de clientes. Permiso `customers`.
GET /customer-groups/ Listar customer-groups read_customers
Permiso: `read_customers`.
curl -s -X GET "$RM_BASE_URL/customer-groups/" \
-H "Authorization: Bearer $RM_API_KEY"
POST /customer-groups/ Crear el grupo de clientes write_customers
El cuerpo lleva `code`. `409`/`400` si el código ya existe. Permiso: `write_customers`.
curl -s -X POST "$RM_BASE_URL/customer-groups/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"code": "EJEMPLO",
"name": "Mayoristas",
"description": "Compra por caja"
}'
GET /customer-groups/{code}/ Obtener el grupo de clientes read_customers
Permiso: `read_customers`.
curl -s -X GET "$RM_BASE_URL/customer-groups/{code}/" \
-H "Authorization: Bearer $RM_API_KEY"
PUT /customer-groups/{code}/ Crear o reemplazar el grupo de clientes (upsert) write_customers
`201` si lo crea, `200` si lo reemplaza. El código de la ruta manda sobre el del cuerpo. Permiso: `write_customers`.
curl -s -X PUT "$RM_BASE_URL/customer-groups/{code}/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Mayoristas",
"description": "Compra por caja"
}'
PATCH /customer-groups/{code}/ Actualizar campos de el grupo de clientes write_customers
Solo los campos enviados; `404` si no existe. Permiso: `write_customers`.
curl -s -X PATCH "$RM_BASE_URL/customer-groups/{code}/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Mayoristas"
}'
DELETE /customer-groups/{code}/ Eliminar el grupo de clientes write_customers
`400` si otros registros dependen de este. Permiso: `write_customers`.
curl -s -X DELETE "$RM_BASE_URL/customer-groups/{code}/" \
-H "Authorization: Bearer $RM_API_KEY"
GET /customers/ Listar clientes read_customers
Permiso: `read_customers`.
curl -s -X GET "$RM_BASE_URL/customers/" \
-H "Authorization: Bearer $RM_API_KEY"
POST /customers/ Crear cliente write_customers
Permiso: `write_customers`.
curl -s -X POST "$RM_BASE_URL/customers/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"code": "C-100",
"first_name": "Carlos",
"last_name": "Hernández",
"company": "Uniformes del Bajío",
"email": "compras@ejemplo.com",
"phone": "477 555 0101",
"group": "MAYORISTAS",
"accepts_marketing": false,
"addresses": [
{
"label": "Matriz",
"line1": "Blvd. López Mateos 3401",
"city": "León",
"state": "Guanajuato",
"postal_code": "37160",
"country": "MX"
}
]
}'
GET /customers/{id}/ Obtener cliente read_customers
Permiso: `read_customers`.
curl -s -X GET "$RM_BASE_URL/customers/{id}/" \
-H "Authorization: Bearer $RM_API_KEY"
PUT /customers/{id}/ Reemplazar cliente write_customers
`addresses` es declarativo: si lo envías reemplaza todas las direcciones. Permiso: `write_customers`.
curl -s -X PUT "$RM_BASE_URL/customers/{id}/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"code": "C-100",
"first_name": "Carlos",
"last_name": "Hernández",
"company": "Uniformes del Bajío",
"email": "compras@ejemplo.com",
"phone": "477 555 0101",
"group": "MAYORISTAS",
"accepts_marketing": false,
"addresses": [
{
"label": "Matriz",
"line1": "Blvd. López Mateos 3401",
"city": "León",
"state": "Guanajuato",
"postal_code": "37160",
"country": "MX"
}
]
}'
PATCH /customers/{id}/ Actualizar campos de un cliente write_customers
Permiso: `write_customers`.
curl -s -X PATCH "$RM_BASE_URL/customers/{id}/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone": "477 555 0102"
}'
DELETE /customers/{id}/ Eliminar cliente write_customers
Permiso: `write_customers`.
curl -s -X DELETE "$RM_BASE_URL/customers/{id}/" \
-H "Authorization: Bearer $RM_API_KEY"
Pedidos
Pedidos, cambios de estado, pagos, devoluciones, reembolsos, paqueterías y medios de pago. Permiso `orders`.
GET /orders/ Listar pedidos read_orders
Orden de alta (no por número). Para el pull del ERP usa `updated_since` o `placed_since`. Permiso: `read_orders`.
curl -s -X GET "$RM_BASE_URL/orders/" \
-H "Authorization: Bearer $RM_API_KEY"
POST /orders/ Crear pedido write_orders
`201` la primera vez; con `Idempotency-Key` repetida y los mismos datos, `200` con el mismo pedido. Rechaza (400) sin existencia o sin precio. Permiso: `write_orders`.
curl -s -X POST "$RM_BASE_URL/orders/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"customer_email": "compras@ejemplo.com",
"lines": [
{
"sku": "PLY-001-N-CH",
"quantity": "6"
},
{
"sku": "COC-002",
"quantity": "1",
"unit_price": "14.50"
}
],
"shipping_address": {
"line1": "Blvd. Los Próceres 12",
"city": "Antiguo Cuscatlán",
"state": "La Libertad",
"country": "SV"
},
"location": "CEDIS",
"discount": "0",
"shipping": "8.00",
"notes": ""
}'
GET /orders/{id}/ Obtener pedido read_orders
Permiso: `read_orders`.
curl -s -X GET "$RM_BASE_URL/orders/{id}/" \
-H "Authorization: Bearer $RM_API_KEY"
POST /orders/{id}/pay/ Registrar pago write_orders
Responde con el pedido completo, o `409` si el paso no aplica. Con `Idempotency-Key` responde `200` siempre y no registra otro pago. Permiso: `write_orders`.
curl -s -X POST "$RM_BASE_URL/orders/{id}/pay/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"method": "transfer",
"reference": "T-123"
}'
POST /orders/{id}/prepare/ Marcar como preparado / aceptado por el ERP write_orders
Responde con el pedido completo, o `409` si el paso no aplica. Deja el sello informativo `erp_accepted_at`; no libera ni descuenta existencia. Permiso: `write_orders`.
curl -s -X POST "$RM_BASE_URL/orders/{id}/prepare/" \
-H "Authorization: Bearer $RM_API_KEY"
POST /orders/{id}/ship/ Marcar como enviado (descuenta existencia) write_orders
Responde con el pedido completo, o `409` si el paso no aplica. Con `carrier_code` calcula `tracking_url`; con `carrier` (texto) busca primero entre las paqueterías de la tienda. Permiso: `write_orders`.
curl -s -X POST "$RM_BASE_URL/orders/{id}/ship/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"carrier_code": "C807",
"tracking_number": "ABC123"
}'
POST /orders/{id}/ready/ Marcar listo para recoger (descuenta existencia) write_orders
Responde con el pedido completo, o `409` si el paso no aplica. Pedidos `pickup`. Permiso: `write_orders`.
curl -s -X POST "$RM_BASE_URL/orders/{id}/ready/" \
-H "Authorization: Bearer $RM_API_KEY"
POST /orders/{id}/deliver/ Marcar como entregado write_orders
Responde con el pedido completo, o `409` si el paso no aplica. Desde `shipped` o `ready_for_pickup`. Permiso: `write_orders`.
curl -s -X POST "$RM_BASE_URL/orders/{id}/deliver/" \
-H "Authorization: Bearer $RM_API_KEY"
POST /orders/{id}/cancel/ Cancelar pedido write_orders
Responde con el pedido completo, o `409` si el paso no aplica. Libera lo apartado; reembolsa por el pago si ya estaba cobrado. Permiso: `write_orders`.
curl -s -X POST "$RM_BASE_URL/orders/{id}/cancel/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"reason": "El cliente lo pidió"
}'
POST /orders/{id}/archive/ Archivar pedido write_orders
Responde con el pedido completo, o `409` si el paso no aplica. Solo cancelados, entregados o reembolsados. Permiso: `write_orders`.
curl -s -X POST "$RM_BASE_URL/orders/{id}/archive/" \
-H "Authorization: Bearer $RM_API_KEY"
POST /orders/{id}/unarchive/ Desarchivar pedido write_orders
Responde con el pedido completo, o `409` si el paso no aplica. Lo regresa a como estaba. Permiso: `write_orders`.
curl -s -X POST "$RM_BASE_URL/orders/{id}/unarchive/" \
-H "Authorization: Bearer $RM_API_KEY"
POST /orders/{id}/returns/ Registrar devolución write_orders
`201` y luego `200` al repetir con la misma `Idempotency-Key`. Permiso: `write_orders`.
curl -s -X POST "$RM_BASE_URL/orders/{id}/returns/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"lines": [
{
"line_id": "0192f3a4-6b1c-7d2e-9f00-1a2b3c4d5e6f",
"quantity": "1"
}
],
"reason": "Talla",
"restock": true
}'
POST /orders/{id}/refunds/ Reembolsar un pago write_orders
Síncrono: con pasarela se pide en la misma llamada y, si falla, `409` sin rastro. `200` al repetir con la misma llave. Permiso: `write_orders`.
curl -s -X POST "$RM_BASE_URL/orders/{id}/refunds/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"amount": "25.00",
"reason": "Producto dañado"
}'
GET /orders/{id}/payments/ Listar pagos del pedido read_orders
Solo lectura; cada pago trae sus `refunds`. Permiso: `read_orders`.
curl -s -X GET "$RM_BASE_URL/orders/{id}/payments/" \
-H "Authorization: Bearer $RM_API_KEY"
GET /carriers/ Listar paqueterías read_orders
Solo lectura. Incluye las desactivadas. Permiso: `read_orders`.
curl -s -X GET "$RM_BASE_URL/carriers/" \
-H "Authorization: Bearer $RM_API_KEY"
GET /carriers/{code}/ Obtener paquetería read_orders
Permiso: `read_orders`.
curl -s -X GET "$RM_BASE_URL/carriers/{code}/" \
-H "Authorization: Bearer $RM_API_KEY"
GET /payment-methods/ Listar medios de pago read_orders
Solo lectura. Incluye los desactivados. Permiso: `read_orders`.
curl -s -X GET "$RM_BASE_URL/payment-methods/" \
-H "Authorization: Bearer $RM_API_KEY"
GET /payment-methods/{code}/ Obtener medio de pago read_orders
Permiso: `read_orders`.
curl -s -X GET "$RM_BASE_URL/payment-methods/{code}/" \
-H "Authorization: Bearer $RM_API_KEY"
Integraciones
Conexiones, corridas de sincronización y referencias externas. Permiso `integrations`.
GET /connections/ Listar conexiones read_integrations
Solo lectura; las conexiones se crean en el back office. Permiso: `read_integrations`.
curl -s -X GET "$RM_BASE_URL/connections/" \
-H "Authorization: Bearer $RM_API_KEY"
GET /connections/{id}/ Obtener conexión read_integrations
Permiso: `read_integrations`.
curl -s -X GET "$RM_BASE_URL/connections/{id}/" \
-H "Authorization: Bearer $RM_API_KEY"
GET /connections/{id}/sync-runs/ Listar corridas de sincronización read_integrations
Permiso: `read_integrations`.
curl -s -X GET "$RM_BASE_URL/connections/{id}/sync-runs/" \
-H "Authorization: Bearer $RM_API_KEY"
POST /connections/{id}/sync-runs/ Reportar una corrida de sincronización write_integrations
Actualiza `last_sync_at` de la conexión a `finished_at` (nunca lo retrocede; una corrida `failed` no lo mueve). Permiso: `write_integrations`.
curl -s -X POST "$RM_BASE_URL/connections/{id}/sync-runs/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"kind": "inventory",
"started_at": "2026-10-17T10:00:00Z",
"finished_at": "2026-10-17T10:05:00Z",
"records": 1200,
"errors": 3,
"message": "3 SKU sin variante"
}'
GET /external-references/ Buscar referencias externas read_integrations
Permiso: `read_integrations`.
curl -s -X GET "$RM_BASE_URL/external-references/" \
-H "Authorization: Bearer $RM_API_KEY"
POST /external-references/ Guardar referencia externa (upsert) write_integrations
Upsert por (conexión, registro local): `201` si crea, `200` si actualiza. Un `external_id` no puede ligarse a dos registros. Permiso: `write_integrations`.
curl -s -X POST "$RM_BASE_URL/external-references/" \
-H "Authorization: Bearer $RM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"connection": "0192f3a4-6b1c-7d2e-9f00-1a2b3c4d5e6f",
"object_type": "product",
"object_key": "PLY-001",
"external_id": "5c2e0000-0000-0000-0000-000000000000",
"external_code": "1000"
}'
DELETE /external-references/{id}/ Borrar referencia externa write_integrations
Permiso: `write_integrations`.
curl -s -X DELETE "$RM_BASE_URL/external-references/{id}/" \
-H "Authorization: Bearer $RM_API_KEY"
Webhooks salientes
La app Webhooks de la tienda manda un POST con JSON a la dirección que configures, por cada evento elegido. Solo se aceptan direcciones https públicas; no se siguen redirecciones y la espera máxima es de 5 s.
Eventos: order.created, order.updated, order.paid, order.payment_failed, order.shipped, order.ready_for_pickup, order.delivered, order.cancelled, order.returned, order.refunded, inventory.changed.
{"id": "6f1c0000-0000-0000-0000-000000000000", "event": "order.created", "store": "mia",
"occurred_at": "2026-10-15T20:31:02+00:00", "data": {"number": 1001, "payment_status": "pending", "...": "..."}}
Cabeceras: X-RM-Event, X-RM-Event-Id (igual a id), X-RM-Signature y User-Agent: RM-Webhooks/1.
Verificar la firma
X-RM-Signature es el HMAC-SHA256 en hexadecimal del cuerpo exacto (los bytes recibidos, sin parsear y volver a serializar), con el secreto de la app como llave. Compara en tiempo constante.
import hashlib, hmac
def firma_valida(cuerpo: bytes, cabecera: str, secreto: str) -> bool:
esperada = hmac.new(secreto.encode(), cuerpo, hashlib.sha256).hexdigest()
return hmac.compare_digest(esperada, cabecera)
import { createHmac, timingSafeEqual } from "node:crypto";
export function firmaValida(cuerpo, cabecera, secreto) {
const esperada = createHmac("sha256", secreto).update(cuerpo).digest("hex");
const a = Buffer.from(esperada), b = Buffer.from(cabecera ?? "");
return a.length === b.length && timingSafeEqual(a, b);
}
Entrega y reintentos
- Responde
2xxpara confirmar. Con5xx,429,408, tiempo agotado o error de red se reintenta con espera creciente (30 s · 2n, máximo 1 h): hasta 8 intentos en unas 2 horas. Cualquier otro4xxse da por fallido sin reintentar. Cada reintento manda el mismo cuerpo, el mismoidy la misma firma. - Al menos una vez: una entrega puede llegar repetida. Deduplica por
id. - Sin orden garantizado: compara
occurred_at; eninventory.changedignora el evento más viejo que el último aplicado para ese SKU. - Tras 5 entregas fallidas seguidas la app se pausa y la tienda ve un aviso.
order.refundedtrae ademásrefund(id,payment_id,amount,currency,status,reason).
Errores y límites
| Código | Significa |
|---|---|
200 201 204 | Correcto. 201 crea; 204 sin cuerpo. |
202 | Trabajo masivo encolado: consulta su id. |
400 | Validación. Detalle por campo, por ejemplo {"sku": ["No existe el SKU «X»."]}. En operaciones masivas, {"errors": {"<índice>": [...]}}. |
401 | Sin llave, o llave inválida o desactivada. |
403 | La llave no tiene el permiso: {"detail": "La llave no tiene el permiso «write_orders»."} |
404 | No existe en la tienda de la llave (también si es de otra tienda). |
409 | El paso no aplica al estado actual (por ejemplo, enviar un pedido cancelado), o una Idempotency-Key se usó con otros datos. |
429 | Presupuesto agotado. Espera Retry-After segundos. |
Límite de peticiones
Cada llave tiene su propio presupuesto de 600 peticiones por minuto (ventana deslizante). Una llave nunca gasta el presupuesto de otra. Cada respuesta autenticada trae:
X-RateLimit-Limit: peticiones permitidas por minuto.X-RateLimit-Remaining: las que quedan en la ventana.X-RateLimit-Reset: segundo (epoch Unix) en que se libera el cupo más antiguo.
Al agotarlo, 429 con Retry-After. Las peticiones rechazadas no cuentan: esperar ese tiempo basta. Reintenta con espera creciente solo 429, 5xx y errores de red; nunca un 4xx. Para cargas grandes usa los endpoints masivos (hasta 1000 filas por llamada en precios e inventario, 500 productos por llamada en el alta masiva).
Guía de conector
El flujo típico de un ERP contra RM, en el orden en que conviene construirlo. Cada paso es idempotente: puedes repetirlo sin duplicar.
Probar conexión y permisos
GET /. Confirma la tienda y que la llave trae los scopes que necesitas.Catálogo
Crea antes lo que los productos referencian: categorías (
PUT /categories/{code}/), marcas, atributos y ubicaciones. Luego los productos:PUT /products/{code}/uno a uno,POST /products/bulk/(hasta 500, upsert) oPOST /products/import/para primeras cargas de cientos de miles (NDJSON, solo crea). Si tu ERP ya tiene variantes, usavariants_mode: "exact". Revisa el resultado de los trabajos fila por fila.Precios
POST /prices/bulk/?partial=trueen bloques de 1000, por lista de precios.price: nullborra la línea.Inventario
POST /inventory/bulk/?partial=trueconon_handabsoluto (lo que dice tu ERP). Cada pedido de RM aparta su cantidad al crearse; al enviarlo se descuenta. Tu número manda: mientras un pedido sigue abierto en ambos sistemas, RM muestra menos que el ERP, nunca más.Pull de pedidos
Cada pocos minutos:
GET /orders/?updated_since=<última marca>(o?placed_since=), siguiendonext. Guarda la marca de la última corrida exitosa, no la hora de tu reloj. Con el webhookorder.createdpuedes enterarte al instante; el pull sigue siendo la red de seguridad.Confirmar que el ERP lo recibió
POST /external-references/conobject_type: "order"y el número del pedido liga el pedido con su documento en el ERP;POST /orders/{id}/prepare/deja el selloerp_accepted_at. Ninguno libera ni descuenta existencia.Push de estados
Cuando el ERP avance:
pay(cobrado),ship(concarrier_codeytracking_number),ready(listo para recoger),deliver,cancel,returnsyrefunds. Responden con el pedido completo, o409si el paso no aplica. UsaIdempotency-Keyenpay,returnsyrefunds.Reportar la corrida
POST /connections/{id}/sync-runs/con el tipo, las horas, los registros y los errores: la tienda ve en RM cuándo sincronizó y cómo le fue.
Descargas
- openapi.json OpenAPI 3.1: ábrelo en Postman, Insomnia, Swagger Editor o pégalo en un asistente de IA.
- rm-api.postman_collection.json Colección de Postman v2.1 por áreas, con cuerpos de ejemplo y «Probar conexión».
- rm-api.postman_environment.json Environment con
base_urlyapi_key(vacía: pon la tuya). - api.md Referencia completa con todas las reglas de negocio.
- api-ia.md y llms.txt Contexto y prompts para IA.
En Postman: importa la colección y el environment, elige el environment rm-api, pega tu llave en api_key y ejecuta «Probar conexión».
Implementar con IA
Pega el contexto y el prompt que necesites en tu asistente de código, y adjunta openapi.json. Nunca pegues una llave real: usa una variable de entorno. Todo esto también está en api-ia.md y, en formato para agentes, en /llms.txt.
Contexto compacto de la API
Pégalo siempre, antes del prompt.
API REST v1 de RM (ecommerce multi-tenant). Base: {base_url} (p. ej. https://retail-s.online/api/v1). JSON UTF-8.
TENANT Y LLAVES
- Cada llave de API pertenece a UNA tienda: todo lo que lee o escribe es de esa tienda. No hay forma de elegir otra.
- Autenticación: cabecera "Authorization: Bearer rk_<id>_<secreto>". La sesión del back office no sirve.
- GET {base_url}/ devuelve la tienda, el nombre de la llave y sus scopes (prueba de conexión).
SCOPES (por área; GET pide read_<área>, POST/PUT/PATCH/DELETE piden write_<área>; escribir incluye leer)
- products: productos, variantes, imágenes, categorías, marcas, colecciones, atributos, plantillas, modificadores, traducciones, alta masiva, importación.
- inventory: inventario y ubicaciones. prices: precios y listas. customers: clientes y grupos.
- orders: pedidos, estados, pagos, devoluciones, reembolsos, paqueterías y medios de pago.
- integrations: conexiones, corridas de sincronización y referencias externas.
- Sin el permiso: 403 {"detail": "La llave no tiene el permiso «write_orders»."}.
IDENTIDAD DE LOS REGISTROS
- Con código (productos, categorías, marcas, colecciones, atributos, ubicaciones, listas de precios, grupos): el código, en mayúsculas (ply-001 = PLY-001).
- Variantes: por SKU. Clientes, pedidos, líneas, devoluciones, imágenes, conexiones y referencias externas: "id" público UUID (guárdalo como texto).
- PUT /<recurso>/<código>/ es upsert (201 crea, 200 reemplaza). PATCH actualiza solo lo enviado (404 si no existe). Los arreglos "options", "variants" (con variants_mode "exact"), "addresses" y "attributes" son DECLARATIVOS: lo que envías es lo que queda.
- Montos y cantidades: texto decimal ("199.0000"). Fechas: ISO 8601 con zona.
PAGINACIÓN Y SINCRONIZACIÓN
- Listados por cursor: ?limit= (1 a 500, por omisión 100). Respuesta {"next": url|null, "results": [...]}. Pide la URL "next" TAL CUAL hasta que sea null. No hay count, previous ni offset (offset da 400).
- Incremental: ?updated_since=<ISO 8601> en los listados por código y en clientes y pedidos; pedidos también ?placed_since=.
- Los resultados de trabajos masivos (bulk/import) se paginan con ?offset= y ?limit= en el trabajo, no por cursor.
IDEMPOTENCIA
- Cabecera opcional "Idempotency-Key" (hasta 100 caracteres) en POST /orders/, POST /orders/{id}/pay/, /returns/ y /refunds/.
- Misma llave y mismos datos: no repite la operación (201 la primera vez y 200 al repetir; pay siempre 200). Misma llave con otros datos: 409.
- Sin la cabecera, un reintento puede duplicar (pay repetido da 409 "ya está pagado").
CÓDIGOS DE RESPUESTA
- 200/201/204 correcto; 202 trabajo masivo encolado.
- 400 validación (detalle por campo; en bloques, {"errors": {"<índice>": [...]}}). 401 llave ausente, inválida o desactivada. 403 falta el scope. 404 no existe en tu tienda. 409 el paso no aplica al estado, o Idempotency-Key reutilizada con otros datos.
- 429: 600 peticiones por minuto por llave. Espera "Retry-After" segundos. Las respuestas autenticadas traen X-RateLimit-Limit, X-RateLimit-Remaining y X-RateLimit-Reset (epoch), salvo en 401 y 403.
- Reintenta con espera creciente solo 429, 5xx y errores de red; nunca un 4xx.
OPERACIONES MASIVAS
- POST /prices/bulk/ y POST /inventory/bulk/: hasta 1000 filas. Por omisión todo o nada; ?partial=true aplica las válidas y responde 200 con "rows" por fila. inventory on_hand es ABSOLUTO (lo que dice el ERP), no un ajuste.
- POST /products/bulk/ (hasta 500 productos, upsert, 202 + id de trabajo; consulta GET /products/bulk/{id}/).
- POST /products/import/ (multipart, NDJSON, solo crea, 202; resultado en GET /products/import/{id}/rows/).
PEDIDOS
- Estados separados: status (open|cancelled|archived), payment_status, fulfillment_status; delivery_type (shipping|pickup|takeout|dine_in|delivery_app).
- POST /orders/ rechaza (400) sin existencia o sin precio. Cambios: POST /orders/{id}/{pay,prepare,ship,ready,deliver,cancel,archive,unarchive,returns,refunds}/ (409 si el paso no aplica).
- Inventario: cada pedido aparta al crearse; ship/ready descuenta; cancelar libera. /prepare/ solo deja el sello informativo erp_accepted_at.
WEBHOOKS SALIENTES (RM -> tu sistema; app "Webhooks" de la tienda)
- POST JSON a una URL https pública: {"id", "event", "store", "occurred_at", "data"}.
- Eventos: order.created, order.updated, order.paid, order.payment_failed, order.shipped, order.ready_for_pickup, order.delivered, order.cancelled, order.returned, order.refunded, inventory.changed.
- Cabeceras: X-RM-Event, X-RM-Event-Id (= id), X-RM-Signature = HMAC-SHA256 hexadecimal del CUERPO EXACTO (bytes sin reserializar) con el secreto de la app; User-Agent RM-Webhooks/1.
- Responde 2xx rápido (espera máxima 5 s). Reintentos con 5xx, 429, 408 o error de red: hasta 8 en ~2 h, mismo cuerpo, mismo id, misma firma. Otro 4xx no se reintenta.
- Al menos una vez y sin orden garantizado: deduplica por id y compara occurred_at (inventory.changed: ignora el más viejo que el último aplicado por SKU).
- Tras 5 fallos seguidos la app se pausa.
1. Conector de catálogo e inventario desde un ERP
Pega junto con: el contexto compacto, `openapi.json`, y un ejemplo de 3 a 5 artículos de tu ERP (JSON o CSV, sin datos reales de clientes).
Eres un ingeniero de integraciones senior. Con el contexto y el OpenAPI de la API de RM que te adjunto, escribe un conector
que sincroniza el catálogo y el inventario desde mi ERP hacia RM. Lenguaje: <Python 3.12 | Node 20>. ERP de origen: <nombre>.
Requisitos:
1. Lee la llave desde la variable de entorno RM_API_KEY y la base desde RM_BASE_URL. Nada de secretos en el código.
2. Primera carga: productos por POST /products/bulk/ (lotes de 500) o, si son más de 20 000, POST /products/import/ con NDJSON. Consulta
el trabajo hasta que termine y guarda en un archivo de errores las filas con status "error".
3. Carga incremental: usa updated_since del ERP; envía PUT /products/{code}/ solo de lo que cambió. Si el ERP ya tiene variantes, usa variants_mode "exact".
4. Precios: POST /prices/bulk/?partial=true en bloques de 1000, uno por lista de precios. Existencias: POST /inventory/bulk/?partial=true con
on_hand ABSOLUTO. Reporta cada fila fallida sin detener el lote.
5. Guarda el mapa ERP <-> RM con POST /external-references/ (object_type product o variant) y reporta cada corrida con POST /connections/{id}/sync-runs/.
6. Reintentos: espera creciente en 429 (respeta Retry-After), 5xx y errores de red; nunca reintentes un 4xx. Respeta el límite de 600 peticiones por minuto.
7. Código claro, con registro (logging) sin datos personales, una prueba por cada función que transforma datos y un README con cómo ejecutarlo.
Entrega: estructura de archivos, el código completo y los comandos para probarlo contra una tienda de pruebas. Si el contrato no cubre algo, dilo en vez de inventar un endpoint.
2. Consumidor de webhooks de pedidos con verificación de firma
Pega junto con: el contexto compacto y, si quieres ejemplos reales, un cuerpo de evento de pruebas.
Con el contexto de la API de RM que te adjunto, escribe un receptor HTTP de webhooks de pedidos. Framework: <FastAPI | Express | Django>.
Requisitos:
1. Verifica X-RM-Signature: HMAC-SHA256 en hexadecimal sobre el cuerpo EXACTO recibido (bytes crudos, sin parsear ni reserializar)
con el secreto de RM_WEBHOOK_SECRET. Compara con una función de tiempo constante. Firma inválida: 401 sin procesar.
2. Responde 2xx en menos de 5 s: guarda el evento en una cola o tabla y procésalo aparte.
3. Deduplica por "id" (X-RM-Event-Id): el mismo evento puede llegar varias veces. Haz el procesamiento idempotente.
4. No asumas orden: compara occurred_at y descarta un evento más viejo que el último aplicado (en inventory.changed, por sku).
5. Maneja order.created, order.paid, order.shipped, order.cancelled y order.refunded; ignora con 2xx los eventos que no conoces.
6. Tras 2xx RM no reintenta; con 5xx, 429 o timeout sí. Distingue errores temporales (5xx) de datos inválidos (4xx).
7. Pruebas: firma válida, firma inválida, evento repetido y evento fuera de orden. Incluye un comando curl que firme un cuerpo de ejemplo con openssl.
Entrega código completo y las pruebas. No registres el secreto ni el cuerpo completo del pedido en los logs.
3. Script de importación masiva
Pega junto con: el contexto compacto, `openapi.json` y una muestra de 5 filas de tu archivo (CSV o Excel) con las columnas reales.
Con el contexto y el OpenAPI de la API de RM que te adjunto, escribe un script de línea de comandos que importe mi archivo de productos <CSV | XLSX> a RM.
Lenguaje: <Python 3.12 | Node 20>. Te paso una muestra de 5 filas.
Requisitos:
1. Mapea las columnas a la estructura de PUT /products/{code}/ (code, name, status, category, options, variants, attributes...). Pide confirmar el mapeo antes de enviar.
2. Modo --dry-run: valida y muestra cuántos productos se crearían y qué filas son inválidas, sin llamar a la API.
3. Envío: POST /products/import/ (NDJSON) para cargas grandes de productos nuevos; POST /products/bulk/ en lotes de 500 si hay que actualizar existentes.
4. Consulta el trabajo hasta que termine (con espera creciente) y escribe un reporte con las filas "error" y su mensaje. Código de salida distinto de 0 si hubo errores.
5. Llave en RM_API_KEY y base en RM_BASE_URL; jamás en el código ni en el archivo de reporte.
6. Es reanudable: puede volver a correr sin duplicar (usa upsert por código o salta los ya creados).
7. Después, opcionalmente, imágenes por URL (POST /products/{code}/images/ con "url" https) y existencias con /inventory/bulk/.
Entrega el script completo, un ejemplo de archivo de entrada y el comando para ejecutarlo.
4. Cliente de la API en Python o Node
Pega junto con: el contexto compacto y `openapi.json`.
Con el OpenAPI y el contexto de la API de RM que te adjunto, genera un cliente pequeño y tipado para <Python 3.12 (httpx) | Node 20 (fetch) con TypeScript>.
Requisitos:
1. Una clase RMClient(base_url, api_key) con la llave en la cabecera Authorization: Bearer.
2. Un método por operación de los recursos: products, variants, inventory, prices, customers y orders (incluidas las acciones pay, ship, cancel, returns y refunds).
3. Los listados son iteradores/generadores que siguen "next" tal cual hasta que sea null; acepta limit y updated_since.
4. Reintentos con espera creciente en 429 (respeta Retry-After), 5xx y errores de red; ningún reintento en 4xx. Errores tipados: ValidationError (400, con el detalle por campo),
AuthError (401/403), NotFound (404) y ConflictError (409).
5. Idempotency-Key opcional en create_order, pay_order, create_return y refund_order; genera un UUID si el llamador no lo da y reutiliza ESE MISMO UUID en todos los reintentos de la misma operación (uno nuevo por operación, nunca uno nuevo por reintento).
6. Decimales como texto (no float); fechas ISO 8601.
7. Pruebas con un servidor simulado para: paginación, 429 con Retry-After y 409 por Idempotency-Key reutilizada.
Entrega el cliente, las pruebas y un ejemplo de uso de 20 líneas. No incluyas ninguna llave real.
5. Revisión de seguridad del conector
Pega junto con: el contexto compacto y el código completo del conector (sin el archivo `.env`).
Actúa como revisor de seguridad. Te paso el contexto de la API de RM y el código de mi conector. Revísalo y dame una lista priorizada (crítico, alto, medio, bajo)
con archivo y línea, el riesgo en una frase y el arreglo concreto.
Busca en particular:
1. Secretos: llave de API (rk_...), secreto de webhooks o credenciales del ERP en el código, en logs, en mensajes de error, en el historial de git o en archivos de ejemplo.
2. Receptor de webhooks: firma HMAC-SHA256 sobre el cuerpo crudo, comparación en tiempo constante, rechazo sin firma, deduplicación por id, límite de tamaño del cuerpo y respuesta rápida.
3. Mínimo privilegio: la llave debe tener solo los scopes que el conector usa (read_orders sin write si solo lee pedidos, etc.).
4. Reintentos: ninguno en 4xx, espera creciente en 429/5xx, respeto de Retry-After, Idempotency-Key en las operaciones que crean o cobran.
5. Validación de datos del ERP antes de enviarlos; manejo de errores que no pierda datos ni los duplique; verificación TLS activa (nunca verify=False).
6. Datos personales de clientes en logs, archivos temporales o reportes.
7. Dependencias desactualizadas o sin versión fija.
No arregles nada sin mostrarme antes el hallazgo. Si algo del contrato de RM no te consta, dilo en vez de suponer.