Ir al contenido
docs

Crear producto

Crea un producto sin variantes.

POST /public/v1/products

Permiso products:write · Idempotency-Key obligatorio · Productos

Con initial_stock registra además un movimiento de entrada en la bodega indicada. El cambio llega a las cajas del negocio por su sincronización normal (en segundos si están en línea; si están sin conexión, al reconectarse).

Parámetros

Encabezados

NombreTipoRequeridoDescripción
Idempotency-KeystringSíObligatorio en todo POST. Un valor único por operación (p. ej. un UUID); reutilízalo solo para reintentar la MISMA operación. Válido 24 h. Máximo 255 caracteres. Formato: ^[A-Za-z0-9_\-:.]{1,255}$.

Cuerpo de la petición

JSON (Content-Type: application/json): un objeto ProductCreate.

Un campo que no esté en la lista se rechaza con VALIDATION_ERROR.

CampoTipoRequeridoDescripción
namestringSíMáximo 200 caracteres.
descriptionstringNoMáximo 5000 caracteres.
description_shortstringNoMáximo 300 caracteres.
skustringNoMáximo 60 caracteres.
category_idstring (uuid) | nullNo—
base_priceintegerSíPrecio base en pesos. Mínimo: 0. Máximo: 10000000000.
final_priceintegerNoPrecio al que vende la caja. Si no se envía, es igual a base_price. Mínimo: 0. Máximo: 10000000000.
percent_discountnumberNoMínimo: 0. Máximo: 100.
tax_classstringNoClase fiscal del ítem. standard hereda del perfil fiscal del negocio. Valores: standard, reduced_5, excluded, exempt, service_19, event_package, bag, passthrough. Por defecto: standard.
activebooleanNoPor defecto: true.
sellablebooleanNofalse = insumo que no se vende solo. Por defecto: true.
base_unitstringNoValores: und, g, kg, lb, oz, ml, l, doc. Por defecto: und.
sold_by_weightbooleanNoRequiere base_unit g, kg o lb. Por defecto: false.
initial_stockintegerNoCrea además un movimiento de entrada con este stock. Mínimo: 1. Máximo: 1000000000.
warehouse_idstring (uuid)NoBodega del stock inicial. Por defecto, la bodega predeterminada.

Ejemplo de petición

El cuerpo del ejemplo lleva solo los campos obligatorios, con valores ilustrativos: ajústalos a tus datos.

Terminal
curl -X POST "https://api.posdata.so/public/v1/products" \
  -H "Authorization: Bearer $POSDATA_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"name":"Café molido 500 g","base_price":25900}'
JavaScript
const res = await fetch('https://api.posdata.so/public/v1/products', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.POSDATA_API_KEY}`,
    'Idempotency-Key': crypto.randomUUID(), // reutilízala solo para reintentar
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "name": "Café molido 500 g",
    "base_price": 25900
  }),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.error.code}: ${data.error.message} (${data.error.request_id})`);
Python
import os
import uuid

import requests

res = requests.post(
    "https://api.posdata.so/public/v1/products",
    headers={
        "Authorization": f"Bearer {os.environ['POSDATA_API_KEY']}",
        "Idempotency-Key": str(uuid.uuid4()),  # reutilízala solo para reintentar
    },
    json={
        "name": "Café molido 500 g",
        "base_price": 25900,
    },
    timeout=30,
)
data = res.json()
if not res.ok:
    raise RuntimeError(f"{data['error']['code']}: {data['error']['message']} ({data['error']['request_id']})")
PHP
<?php
$ch = curl_init('https://api.posdata.so/public/v1/products');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('POSDATA_API_KEY'),
        'Idempotency-Key: ' . bin2hex(random_bytes(16)), // reutilízala solo para reintentar
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'name' => 'Café molido 500 g',
        'base_price' => 25900,
    ]),
]);
$data = json_decode(curl_exec($ch), true);
if (curl_getinfo($ch, CURLINFO_RESPONSE_CODE) >= 400) {
    throw new RuntimeException("{$data['error']['code']}: {$data['error']['message']} ({$data['error']['request_id']})");
}
Idempotency-Key
Usa un valor nuevo por operación y repite el mismo solo para reintentar esa misma operación. Mira Idempotencia.

Respuesta

201 Creado: un objeto Product.

CampoTipoRequeridoDescripción
object"product"Sí—
idstring (uuid)Sí—
namestringSí—
descriptionstringSí—
description_shortstringSí—
skustring | nullSíSKU del producto (null si no tiene).
category_idstring (uuid) | nullSí—
kindstringSícomposite = se arma desde una receta y no descuenta su propio stock. Valores: simple, composite.
sellablebooleanSífalse = insumo que no se vende por sí solo.
activebooleanSí—
tax_classstringSíClase fiscal del ítem. standard hereda del perfil fiscal del negocio. Valores: standard, reduced_5, excluded, exempt, service_19, event_package, bag, passthrough.
base_priceintegerSíPrecio de lista, antes del descuento propio del producto.
final_priceintegerSíPrecio al que el POS vende. Si el producto no tiene descuento es igual a base_price.
percent_discountnumberSíDescuento del producto sobre base_price, en porcentaje (0-100, puede tener decimales).
base_unitstringSíUnidad base (und, g, kg, lb, oz, ml, l, doc). Stock y costo se expresan en ella.
sold_by_weightbooleanSíSe vende por peso: la cantidad de la línea es un peso en base_unit.
stocknumber | nullSíStock oficial en base_unit (suma de todas sus bodegas y variantes; puede tener decimales en productos por peso y ser negativo si una venta ganó la carrera). Es null mientras la cuenta no lleve el stock en la nube (sus cajas aún en una versión anterior), en productos sin control de stock y en los que se arman con receta. Por bodega: GET /inventory/levels. Para moverlo: POST /inventory/movements o POST /inventory/counts.
unit_costnumberSíCosto promedio ponderado por unidad base (informativo).
image_urlstring (uri) | nullSí—
imagesarreglo de string (uri)Sí—
has_variantsbooleanSí—
variantsarreglo de ProductVariantSíVariantes vigentes (no eliminadas).
deleted_atstring (date-time) | nullSí—
created_atstring (date-time)Sí—
updated_atstring (date-time)Sí—
Ejemplo de respuesta (valores ilustrativos)
{
  "object": "product",
  "id": "3f1c2e8d-7a10-4d0b-9a57-0b6f2f0e5c1d",
  "name": "Café molido 500 g",
  "description": "texto",
  "description_short": "texto",
  "sku": "CAF-500",
  "category_id": "3f1c2e8d-7a10-4d0b-9a57-0b6f2f0e5c1d",
  "kind": "simple",
  "sellable": true,
  "active": true,
  "tax_class": "standard",
  "base_price": 25900,
  "final_price": 25900,
  "percent_discount": 19,
  "base_unit": "texto",
  "sold_by_weight": true,
  "stock": 5,
  "unit_cost": 1,
  "image_url": "https://ejemplo.com",
  "images": [
    "https://ejemplo.com"
  ],
  "has_variants": true,
  "variants": [
    {
      "object": "product_variant",
      "id": "3f1c2e8d-7a10-4d0b-9a57-0b6f2f0e5c1d",
      "product_id": "3f1c2e8d-7a10-4d0b-9a57-0b6f2f0e5c1d",
      "label": "texto",
      "sku": "CAF-500",
      "base_price": 25900,
      "final_price": 25900,
      "percent_discount": 19,
      "images": [
        "https://ejemplo.com"
      ],
      "is_default": true,
      "active": true,
      "stock": 5,
      "created_at": "2026-09-28T15:04:05.000Z",
      "updated_at": "2026-09-28T15:04:05.000Z"
    }
  ],
  "deleted_at": "2026-09-28T15:04:05.000Z",
  "created_at": "2026-09-28T15:04:05.000Z",
  "updated_at": "2026-09-28T15:04:05.000Z"
}

Errores

Todo error responde con un objeto Error y un request_id. Ramifica por error.code, nunca por el mensaje.

Cuerpo: un objeto Error. Qué significa cada código y qué hacer: Errores.

VALIDATION_ERRORINVALID_JSONINVALID_CURSOR

Cuerpo: un objeto Error. Qué significa cada código y qué hacer: Errores.

UNAUTHENTICATEDINVALID_API_KEYAPI_KEY_REVOKEDAPI_KEY_EXPIREDAPI_KEY_WRONG_ENVIRONMENTACCOUNT_INACTIVE

Cuerpo: un objeto Error. Qué significa cada código y qué hacer: Errores.

PLAN_REQUIREDINSUFFICIENT_SCOPEBROWSER_REQUESTS_NOT_ALLOWED

Cuerpo: un objeto Error. Qué significa cada código y qué hacer: Errores.

CONFLICTSKU_ALREADY_EXISTSIDEMPOTENCY_IN_PROGRESSNO_WAREHOUSEDEVICES_OUTDATEDINSUFFICIENT_STOCKSTOCK_MODE_LEGACY

Cuerpo: un objeto Error. Qué significa cada código y qué hacer: Errores.

IDEMPOTENCY_KEY_REUSEDCOMPOSITE_STOCK_UNSUPPORTED

Cuerpo: un objeto Error. Qué significa cada código y qué hacer: Errores.

RATE_LIMITED

Cuerpo: un objeto Error. Qué significa cada código y qué hacer: Errores.

INTERNAL_ERROR