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.

Permisos por área
ÁreaScopesRecursos
Catálogoread_products write_productsproductos, variantes, imágenes, categorías, marcas, colecciones, atributos, plantillas, modificadores, traducciones, alta masiva e importación
Inventarioread_inventory write_inventoryexistencias, ubicaciones
Preciosread_prices write_pricesprecios, listas de precios
Clientesread_customers write_customersclientes, grupos de clientes
Pedidosread_orders write_orderspedidos, cambios de estado, pagos, devoluciones, reembolsos, paqueterías, medios de pago
Integracionesread_integrations write_integrationsconexiones, 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-001 y PLY-001 son lo mismo. Las variantes, por SKU. Clientes, pedidos, líneas, devoluciones, imágenes, conexiones y referencias externas, por su id público: un UUID que debes guardar como texto. La API nunca expone IDs internos.
  • Upsert. PUT /<recurso>/<código>/ crea (201) o reemplaza (200). PATCH actualiza solo los campos enviados y da 404 si no existe.
  • Declarativo. Las listas options, variants (con variants_mode: "exact"), addresses, attributes, modifiers y hours dejan 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 de next tal cual hasta que sea null. Los registros salen en orden de alta, así que nada se repite ni se salta mientras lees. No hay count ni previous, y offset responde 400.
  • Sincronización incremental. ?updated_since=2026-10-14T00:00:00Z en los listados; en pedidos también ?placed_since=.
  • Reintentos seguros. POST /orders/, /pay/, /returns/ y /refunds/ aceptan la cabecera Idempotency-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 2xx para confirmar. Con 5xx, 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 otro 4xx se da por fallido sin reintentar. Cada reintento manda el mismo cuerpo, el mismo id y la misma firma.
  • Al menos una vez: una entrega puede llegar repetida. Deduplica por id.
  • Sin orden garantizado: compara occurred_at; en inventory.changed ignora 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.refunded trae además refund (id, payment_id, amount, currency, status, reason).

Errores y límites

Códigos de respuesta
CódigoSignifica
200 201 204Correcto. 201 crea; 204 sin cuerpo.
202Trabajo masivo encolado: consulta su id.
400Validación. Detalle por campo, por ejemplo {"sku": ["No existe el SKU «X»."]}. En operaciones masivas, {"errors": {"<índice>": [...]}}.
401Sin llave, o llave inválida o desactivada.
403La llave no tiene el permiso: {"detail": "La llave no tiene el permiso «write_orders»."}
404No existe en la tienda de la llave (también si es de otra tienda).
409El paso no aplica al estado actual (por ejemplo, enviar un pedido cancelado), o una Idempotency-Key se usó con otros datos.
429Presupuesto 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.

  1. Probar conexión y permisos

    GET /. Confirma la tienda y que la llave trae los scopes que necesitas.

  2. 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) o POST /products/import/ para primeras cargas de cientos de miles (NDJSON, solo crea). Si tu ERP ya tiene variantes, usa variants_mode: "exact". Revisa el resultado de los trabajos fila por fila.

  3. Precios

    POST /prices/bulk/?partial=true en bloques de 1000, por lista de precios. price: null borra la línea.

  4. Inventario

    POST /inventory/bulk/?partial=true con on_hand absoluto (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.

  5. Pull de pedidos

    Cada pocos minutos: GET /orders/?updated_since=<última marca> (o ?placed_since=), siguiendo next. Guarda la marca de la última corrida exitosa, no la hora de tu reloj. Con el webhook order.created puedes enterarte al instante; el pull sigue siendo la red de seguridad.

  6. Confirmar que el ERP lo recibió

    POST /external-references/ con object_type: "order" y el número del pedido liga el pedido con su documento en el ERP; POST /orders/{id}/prepare/ deja el sello erp_accepted_at. Ninguno libera ni descuenta existencia.

  7. Push de estados

    Cuando el ERP avance: pay (cobrado), ship (con carrier_code y tracking_number), ready (listo para recoger), deliver, cancel, returns y refunds. Responden con el pedido completo, o 409 si el paso no aplica. Usa Idempotency-Key en pay, returns y refunds.

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

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.