Conceptos clave
Entendé los fundamentos de TiendaAPI antes de construir tu integración.
Autenticación
TiendaAPI usa dos métodos de autenticación según la API que estés usando:
API de tienda (Storefront)
Para los endpoints públicos del storefront (productos, carrito, checkout), pasá la API key de tu tienda en el header x-api-key:
curl http://localhost:3000/api/store/products \
-H "x-api-key: ta_abc123def456..."La API key identifica a qué tienda pertenece el request. Cada tienda tiene una clave única. Las API keys empiezan con ta_.
API de administración
Para los endpoints de admin (CRUD de productos, gestión de pedidos), primero iniciá sesión para obtener un JWT, luego pasalo en el header Authorization:
# Paso 1: Iniciar sesión
curl -X POST http://localhost:3000/api/admin/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "admin@demo.com", "password": "password123"}'
# Respuesta: { "data": { "token": "eyJhbGciOiJIUzI1NiIs..." } }
# Paso 2: Usar el token
curl http://localhost:3000/api/admin/products \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."Los tokens JWT expiran después de 24 horas. Iniciá sesión de nuevo para obtener uno nuevo.
API de plataforma (interna)
Para crear nuevas tiendas, pasá la clave secreta de plataforma en el header x-platform-key. Esta clave se configura mediante la variable de entorno PLATFORM_SECRET_KEY.
ta_) es segura para el frontend — solo permite lectura y operaciones de carrito.Multi-tenancy
TiendaAPI es multi-tenant desde el primer día. Cada tienda está completamente aislada:
- Tienda A nunca puede ver los productos, pedidos o clientes de la Tienda B
- Cada tienda tiene su propia API key, credenciales de admin y configuración
- El aislamiento de datos se aplica en dos niveles: middleware de la aplicación + Row-Level Security (RLS) de PostgreSQL
Cómo funciona
┌─────────────────────────────────────┐
│ Llega un request a la API │
│ Header: x-api-key: ta_abc123... │
└──────────┬──────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ El middleware resuelve el store_id │
│ desde la API key / JWT / URL slug │
└──────────┬──────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ Cada query a la DB incluye │
│ WHERE store_id = '<id_resuelto>' │
└──────────┬──────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ PostgreSQL RLS garantiza el │
│ aislamiento aunque haya un bug │
└──────────────────────────────────────┘Precios y moneda
Todos los precios en TiendaAPI se almacenan y transmiten como enteros en centavos. Esto evita problemas de precisión con números de punto flotante.
| Valor en la API | Mostrar como |
|---|---|
2500 | $25.00 |
99 | $0.99 |
1000000 | $10,000.00 |
Para mostrar precios en tu frontend:
// JavaScript
function formatPrice(cents) {
return new Intl.NumberFormat('es-AR', {
style: 'currency',
currency: 'ARS',
}).format(cents / 100);
}
formatPrice(2500); // "$25,00"25.00 será rechazado — enviá 2500.Paginación
Todos los endpoints de listado usan paginación basada en cursor. Es más confiable que la paginación por offset para datos en tiempo real.
Cómo funciona
# Primera página (sin cursor)
GET /api/store/products?limit=20
# La respuesta incluye meta:
{
"data": [...],
"meta": {
"has_more": true,
"cursor": "550e8400-e29b-41d4-a716-446655440000",
"count": 20,
"limit": 20
}
}
# Siguiente página (pasá el cursor)
GET /api/store/products?limit=20&cursor=550e8400-e29b-41d4-a716-446655440000Parámetros
| Parámetro | Tipo | Default | Descripción |
|---|---|---|---|
limit | entero | 20 | Ítems por página (1-100) |
cursor | string | — | Cursor de la respuesta anterior |
Meta de la respuesta
| Campo | Tipo | Descripción |
|---|---|---|
has_more | boolean | Si hay más ítems disponibles |
cursor | string | null | Pasá a ?cursor= para la siguiente página |
count | entero | Ítems devueltos en esta página |
limit | entero | Máximo de ítems por página |
Manejo de errores
Todos los errores siguen el formato estructurado RFC 9457. Cada respuesta de error incluye un code legible por máquinas y un message legible por humanos.
{
"status": 422,
"code": "VALIDATION_ERROR",
"message": "La validación del request falló",
"details": {
"errors": {
"price": ["El precio debe ser al menos 1 centavo"],
"slug": ["El slug solo puede contener letras minúsculas, números y guiones"]
}
}
}Códigos de error comunes
| HTTP | Código | Significado |
|---|---|---|
| 400 | BAD_REQUEST | Request inválido (slug duplicado, carrito vacío, etc.) |
| 401 | UNAUTHORIZED | Credenciales de auth faltantes o inválidas |
| 403 | FORBIDDEN | Sin permisos / límite del plan superado |
| 404 | NOT_FOUND | El recurso no existe (o pertenece a otra tienda) |
| 422 | VALIDATION_ERROR | El cuerpo del request falló la validación |
| 429 | RATE_LIMITED | Demasiados requests — revisá el header Retry-After |
| 500 | INTERNAL_ERROR | Error del servidor (por favor reportalo) |
Manejo de errores en código
const res = await fetch('/api/store/products', {
headers: { 'x-api-key': apiKey }
});
if (!res.ok) {
const error = await res.json();
switch (error.code) {
case 'UNAUTHORIZED':
// API key inválida o faltante
break;
case 'RATE_LIMITED':
// Esperar y reintentar
const retryAfter = res.headers.get('Retry-After');
break;
case 'VALIDATION_ERROR':
// Mostrar errores por campo al usuario
console.log(error.details.errors);
break;
}
}Próximos pasos
- Guías prácticas — Guías paso a paso para tareas comunes
- Referencia de API — Documentación interactiva de todos los endpoints