Ir al contenido
docs

Obtener un producto

Permiso requerido: products:read.

GET /public/v1/products/{id}

Permiso products:read · Productos

Parámetros

Parámetros de ruta

NombreTipoRequeridoDescripción
idstring (uuid)Sí—

Parámetros de consulta

NombreTipoRequeridoDescripción
include_deletedbooleanNoSi es true, incluye los registros eliminados (con deleted_at). Por defecto: false.

Ejemplo de petición

Terminal
curl "https://api.posdata.so/public/v1/products/3f1c2e8d-7a10-4d0b-9a57-0b6f2f0e5c1d" \
  -H "Authorization: Bearer $POSDATA_API_KEY"
JavaScript
const res = await fetch('https://api.posdata.so/public/v1/products/3f1c2e8d-7a10-4d0b-9a57-0b6f2f0e5c1d', {
  headers: {
    Authorization: `Bearer ${process.env.POSDATA_API_KEY}`,
  },
});
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 requests

res = requests.get(
    "https://api.posdata.so/public/v1/products/3f1c2e8d-7a10-4d0b-9a57-0b6f2f0e5c1d",
    headers={
        "Authorization": f"Bearer {os.environ['POSDATA_API_KEY']}",
    },
    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/3f1c2e8d-7a10-4d0b-9a57-0b6f2f0e5c1d');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('POSDATA_API_KEY'),
    ],
]);
$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']})");
}

Respuesta

200 OK: 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.

NOT_FOUND

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