Appearance
Productos
CRUD sobre los productos de tu catálogo (servicios, productos simples, con variantes, compuestos). Todas las requests requieren Authorization: Bearer finova_sk_<secret>.
El objeto Product
json
{
"id": "65a0c0f1234567890abcdef0",
"name": "Lentes oftálmicos premium",
"slug": "lentes-oftalmicos-premium",
"sku": "LEN-PREM-001",
"description": "Lentes graduados con armazón de titanio.",
"product_type": "simple",
"is_active": true,
"tags": ["lentes", "premium"],
"category_name": "Lentes",
"product_category": {
"id": "65c2b2c234567890abcdef22",
"name": "Lentes",
"slug": "lentes"
},
"brand": {
"id": "65b1a1b234567890abcdef00",
"name": "Polaroid",
"slug": "polaroid"
},
"gtin": "0049000028911",
"mpn": "POL-PREM-12345",
"condition": "new",
"prices_cents": { "mxn": 450000, "usd": 25000 },
"inventory_enabled": false,
"inventory_mode": "materials",
"taxable": true,
"tax_rate_percent": 16.0,
"image_url": "https://cdn.fi-nova.com/cdn/products/65a0…/image?exp=…&sig=…",
"thumbnail_url": "https://cdn.fi-nova.com/cdn/products/65a0…/image?exp=…&sig=…",
"images": [
{ "url": "https://cdn.fi-nova.com/cdn/products/65a0…/image?exp=…&sig=…", "alt": "Lentes oftálmicos premium", "position": 0 },
{ "url": "https://cdn.fi-nova.com/…/1.jpg", "alt": "Vista lateral", "position": 1 }
],
"material": "Titanio",
"graduado": true,
"voltaje": 110,
"colores": ["rojo", "azul"],
"dimensiones": { "alto_mm": 40, "ancho_mm": 140 },
"ficha_tecnica": { "asset_id": "65e…", "url": "https://cdn.fi-nova.com/…/ficha.pdf", "name": "Ficha técnica", "content_type": "application/pdf" },
"seo": {
"seo_title": "Lentes oftálmicos premium — Óptica XYZ",
"seo_title_translations": { "es": "Lentes oftálmicos premium — Óptica XYZ", "en": "Premium prescription glasses — XYZ Optics" },
"meta_description": "Lentes premium con armazón de titanio. Envío gratis.",
"meta_description_translations": { "es": "Lentes premium…", "en": "Premium glasses…" },
"meta_keywords": "lentes, premium, titanio",
"meta_keywords_translations": { "es": "lentes, premium", "en": "glasses, premium" },
"short_description": "Resumen breve para listados.",
"short_description_translations": { "es": "Resumen breve…", "en": "Short summary…" },
"description": "<p>Contenido HTML largo…</p>",
"description_translations": { "es": "<p>…</p>", "en": "<p>…</p>" },
"og_title": "Lentes premium",
"og_title_translations": { "es": "Lentes premium", "en": "Premium glasses" },
"og_description": "Compártelos en redes sociales.",
"og_description_translations": { "es": "Compártelos…", "en": "Share them…" },
"indexable": true,
"og_image": { "url": "https://cdn.fi-nova.com/…/og.jpg", "alt_translations": { "es": "Lentes premium" } },
"banner": { "url": "https://cdn.fi-nova.com/…/banner.jpg" },
"video": { "url": "https://cdn.fi-nova.com/…/spot.mp4", "content_type": "video/mp4" },
"gallery": [{ "url": "https://cdn.fi-nova.com/…/1.jpg", "alt_translations": {} }],
"og_image_url": "https://cdn.fi-nova.com/…/og.jpg",
"banner_url": "https://cdn.fi-nova.com/…/banner.jpg",
"gallery_urls": ["https://cdn.fi-nova.com/…/1.jpg"]
},
"variants": [
{
"id": "65c2d2c345678901abcdef00",
"name": "Armazón rojo / Talla M",
"sku": "LEN-PREM-R-M",
"prices_cents": { "mxn": 470000 },
"is_active": true,
"options": { "color": "red", "size": "M" },
"image_url": "https://cdn.fi-nova.com/cdn/products/65a0…/variants/65c2…/image?exp=…&sig=…"
}
],
"option_types": [
{
"name": "color", "presentation": "Color", "position": 1,
"values": [
{ "name": "Rojo", "value": "red", "presentation": "Rojo", "position": 1, "extra_price_cents": 0 }
]
}
],
"components": [
{ "id": "65c2…", "name": "Estuche rígido", "component_product_id": "65aa…", "quantity": 1, "is_required": true, "extra_prices_cents": { "mxn": 0 } }
],
"inventory": {
"enabled": true,
"unit": "pcs",
"on_hand_qty": "120.0",
"reserved_qty": "8.0",
"available_qty": "112.0",
"by_location": [
{
"warehouse_id": "65d0…", "warehouse_name": "Bodega Central",
"rack_id": "65d1…", "rack_name": "A-12",
"on_hand_qty": "120.0", "reserved_qty": "8.0", "available_qty": "112.0"
}
]
},
"created_at": "2026-01-15T14:23:11Z",
"updated_at": "2026-05-14T09:01:42Z"
}Lista vs detalle
variants, option_types, components e inventory solo se incluyen en GET /products/:id (detalle). El listado (GET /products) omite estos campos para mantener cada fila ligera, pero sí incluye image_url, thumbnail_url e images.
URLs de imagen firmadas
image_url, thumbnail_url, las URLs en images[] y variants[].image_url son URLs firmadas de corta duración (HMAC + expiración). No las guardes de forma permanente: vuelve a leer el producto para obtener URLs frescas. thumbnail_url apunta a la imagen completa (no se genera un derivado); redimensiona del lado de tu sitio.
Campos
| Campo | Tipo | Descripción |
|---|---|---|
id | string | ObjectId. |
name | string | Nombre comercial. |
slug | string | Identificador amigable opcional. Define la URL pública del producto en tu sitio. |
sku | string | SKU principal (para simple / service). |
description | string | Descripción libre (no localizada). Para el contenido SEO localizado usa seo.description. |
product_type | enum | simple, service, variable, composite. |
is_active | bool | Si está visible/comprable. |
tags | string[] | Etiquetas libres. |
category_name | string | (deprecado, pero presente) Nombre de la categoría de catálogo (denormalizado). Usa product_category para la referencia estructurada. |
product_category | object | { id, name, slug } — referencia denormalizada a la categoría de catálogo. Edita la categoría completa en /product-categories. Ver Categorías de producto. No confundir con la categoría financiera de ingresos/egresos. |
brand | object | { id, name, slug } — referencia denormalizada. Edita la marca completa en /brands. |
gtin | string | GTIN / EAN / UPC / ISBN (8, 12, 13 o 14 dígitos). Útil para Google Merchant. |
mpn | string | Manufacturer Part Number. |
condition | enum | new, used, refurbished, damaged. Default new. |
prices_cents | object | Mapa multi-moneda { <iso4217_lowercase>: <centavos> }, ej. { "mxn": 450000, "usd": 25000 }. Reemplaza a los antiguos currency/price_cents escalares. |
inventory_enabled | bool | Si lleva control de inventario. |
inventory_mode | enum | materials, self, hybrid. |
taxable | bool | Si causa impuesto. |
tax_rate_percent | float | Tasa de impuesto (ej. 16.0). |
image_url | string | null | URL firmada de la imagen principal. null si no hay imagen. |
thumbnail_url | string | null | Igual que image_url (no se genera un derivado; redimensiona en tu sitio). |
images | object[] | Galería plana: { url, alt, position }. La imagen principal va en position: 0, seguida de la galería SEO. |
| (campos personalizados) | any | Cada campo personalizado se devuelve aplanado como una clave de primer nivel del producto (ej. "voltaje": 110), no dentro de un array custom_properties. Ver Campos personalizados. |
seo | object | Contenido localizado + multimedia para tu sitio público. Ver El objeto SEO. |
variants | object[] | (solo detalle) Variantes del producto. Ver Variantes, opciones y componentes. |
option_types | object[] | (solo detalle) Tipos de opción (ej. color, talla) y sus valores. |
components | object[] | (solo detalle) Componentes de un producto composite. |
inventory | object | null | (solo detalle) Disponibilidad: total y desglose por almacén/rack. null si el producto no lleva inventario. Nunca incluye costos. |
created_at / updated_at | string | ISO 8601 UTC. |
Datos no expuestos. Por privacidad, el API público nunca devuelve cifras de venta (
sold_units,sold_amount), costos fijos (fixed_costs) ni la lista de materiales/costos (material_requirements). El objetoinventoryexpone solo cantidades disponibles, no valores monetarios.
El objeto SEO
Anidado bajo seo. Diseñado para alimentar el <head> y el cuerpo de la página pública del producto en tu sitio.
| Campo | Tipo | Descripción |
|---|---|---|
seo_title | string | SEO title resuelto al locale actual. |
seo_title_translations | object | { "es": "…", "en": "…" }. |
meta_description / _translations | string / object | Meta description para buscadores. |
meta_keywords / _translations | string / object | Keywords (legacy SEO, opcional). |
short_description / _translations | string / object | Resumen corto para listados. |
description / _translations | string / object | Contenido largo (HTML permitido). |
og_title / _translations | string / object | Open Graph title para redes sociales. |
og_description / _translations | string / object | Open Graph description. |
indexable | bool | false → emite <meta name="robots" content="noindex,nofollow">. |
og_image | object | { asset_id, url, name, content_type, size_bytes, file_filename, alt_translations }. |
banner | object | Mismo shape que og_image. |
video | object | Mismo shape que og_image. Útil para schema.org/Product.video. |
gallery | object[] | Array con el mismo shape que og_image. |
og_image_url / banner_url / gallery_urls | string / string[] | Legacy URL-only. Espejos del campo enriquecido — preferí los objetos. |
Al escribir vía POST o PATCH, pasa el objeto seo como hijo de data. Los campos localizados se escriben vía *_translations (un hash { locale: valor }). Pasar null/string vacío en una traducción borra esa entrada.
Campos personalizados (specs)
Los campos personalizados son atributos estructurados definidos por el usuario — pensados para la tabla de "Especificaciones técnicas" de la ficha del producto (dimensiones, material, voltaje, etc.).
En la lectura se devuelven aplanados: cada campo aparece como una clave de primer nivel del producto, con su valor directo.
json
{
"id": "65a0…",
"name": "Lentes oftálmicos premium",
"voltaje": 110,
"material": "Titanio",
"colores": ["rojo", "azul"],
"ficha_tecnica": { "asset_id": "65e…", "url": "https://…/ficha.pdf", "name": "Ficha técnica" }
}Claves reservadas
La key es un identificador estilo variable (minúsculas, números y guion bajo; sin espacios ni acentos). Si una clave personalizada coincide con un campo propio del recurso (id, name, slug, sku, prices_cents, seo, brand, …) se descarta al serializar: un campo personalizado nunca puede sobrescribir ni duplicar un campo real. Elige claves propias (ej. voltaje_maximo, material_marco).
En la escritura se pasan como el array custom_properties dentro de data. Cada entrada lleva:
| Campo | Tipo | Descripción |
|---|---|---|
key | string | Requerido. Identificador del atributo. Las entradas con key vacío se descartan. |
type | enum | Cómo interpretar value (ver abajo). Si se omite, se infiere del value. |
value | any | El dato; su forma depende de type. |
Tipos (type) y la forma de value:
type | value |
|---|---|
string | Texto. |
number | Entero o decimal. |
boolean | true / false. |
array | Lista de escalares (["rojo", "azul"]). |
object | Objeto JSON ({ "alto_mm": 40 }). |
object_array | Lista de objetos. |
file | Un media item: { asset_id, url, name, content_type, size_bytes, file_filename, alt_translations } — mismo shape que la multimedia de SEO. |
file_array | Lista de media items. |
Es reemplazo completo: el array que mandas sustituye al anterior; un array vacío [] los borra; omitir la clave los deja intactos. Los valores se sanitizan en el servidor (estructuras anidadas se limpian a JSON seguro; máximo 200 propiedades).
json
{
"data": {
"custom_properties": [
{ "key": "material", "type": "string", "value": "Titanio" },
{ "key": "voltaje", "type": "number", "value": 110 },
{ "key": "ficha_tecnica", "type": "file", "value": { "asset_id": "65e…", "url": "https://…/ficha.pdf", "name": "Ficha técnica" } }
]
}
}Variantes, opciones y componentes
Estas colecciones se leen en el detalle del producto y se escriben (crear/actualizar) anidadas dentro de data, con semántica de nested attributes:
- Crear un item: inclúyelo sin
id. - Actualizar un item existente: inclúyelo con su
id(los demás items no enviados quedan intactos). - Eliminar un item: envíalo con
{ "id": "…", "_destroy": true }.
Los id se preservan entre actualizaciones, por lo que la imagen de una variante (ligada a su id) sobrevive a las ediciones.
variants (para product_type: "variable")
| Campo | Tipo | Descripción |
|---|---|---|
id | string | Omitir para crear. |
name | string | Nombre de la variante. |
sku | string | SKU de la variante. |
prices_cents | object | Mapa multi-moneda { <iso4217>: <centavos> }. Si se omite (o falta una moneda), hereda el precio del producto en esa moneda. |
is_active | bool | Visibilidad. |
options | object | Mapa { tipo: valor }, ej. { "color": "red", "size": "M" }. |
image_url | string | (solo lectura) Súbela con el endpoint de imagen de variante. |
option_types
| Campo | Tipo | Descripción |
|---|---|---|
name | string | Clave interna (ej. color). |
presentation | string | Etiqueta para UI (ej. Color). |
position | integer | Orden. |
values | object[] | { id?, name, value, presentation, position, extra_price_cents, _destroy? }. |
components (para product_type: "composite")
| Campo | Tipo | Descripción |
|---|---|---|
id | string | Omitir para crear. |
name | string | Nombre del componente. |
component_product_id | string | Producto referenciado (opcional). |
quantity | integer | Cantidad incluida. |
is_required | bool | Si es obligatorio. |
extra_prices_cents | object | Mapa multi-moneda { <iso4217>: <centavos> } con el ajuste de precio del componente. |
json
{
"data": {
"name": "Playera premium",
"product_type": "variable",
"prices_cents": { "mxn": 30000 },
"option_types": [
{ "name": "color", "presentation": "Color", "position": 1,
"values": [{ "name": "Rojo", "value": "red", "presentation": "Rojo", "position": 1 }] }
],
"variants": [
{ "name": "Rojo / M", "sku": "PLY-R-M", "prices_cents": { "mxn": 32000 }, "options": { "color": "red", "size": "M" } }
]
}
}Listar productos
GET
/api/v1/productsread-productsLista paginada de los productos de tu empresa. Ordenada por created_at descendente (más nuevos primero). Para catálogos grandes (≥ 5 000 productos) usa cursores; ver la guía de paginación para detalles.
Query params
| Param | Default | Detalle |
|---|---|---|
limit | 25 | Items por página (máx 100). |
search (alias q) | — | Búsqueda full-text/autocomplete sobre name, slug, sku y description. Devuelve hasta los 100 resultados más relevantes, luego paginados. |
brand (alias brand_id) | — | Filtra por marca. Un solo valor o lista separada por comas mezclando ObjectIds y slugs (ej. acme,5f...). Trae los productos de cualquiera de las marcas listadas. Slugs matchean el snapshot denormalizado del producto (sin lookup extra). Un id/slug desconocido no aporta resultados. |
category (alias product_category_id) | — | Filtra por categoría de catálogo. Un solo valor o lista separada por comas de ObjectIds y/o slugs (ej. comida,bebidas). Trae los productos de cualquiera de las categorías listadas. El filtro es jerárquico: filtrar por una categoría padre también trae los productos asignados a cualquiera de sus subcategorías (a cualquier profundidad); filtrar por una subcategoría no trae los productos del padre. Ej.: si el producto A está en 1.1 (hija de 1), category=1 y category=1.1 traen A; pero un producto B en 1 no aparece con category=1.1. Un id/slug desconocido no aporta resultados. |
active | true | Filtra por visibilidad pública. Por default solo trae productos activos (is_active: true). active=false trae solo los inactivos; active=all trae ambos. Cualquier otro valor equivale al default (solo activos). |
starting_after | — | Cursor — id del último item de la página previa. |
ending_before | — | Cursor — id del primer item de la página previa (retrocede). |
include_total | false | Si es true, agrega total y total_pages a meta. Cuenta todo el catálogo, ignora los filtros (costo extra). |
include_filtered_total | false | Si es true, agrega filtered_total y filtered_total_pages a meta con el conteo del resultado ya filtrado (search/q, brand, category, active). Acotado a 10000 (si lo supera, se satura ahí y filtered_total_capped viene en true); con search/q el tope es 100. |
page | 1 | Legacy offset. Máx 200 — más profundo devuelve 400 offset_too_deep. |
El meta siempre incluye has_more, next_cursor y previous_cursor. total/total_pages solo con include_total; filtered_total/filtered_total_pages (y filtered_total_capped) solo con include_filtered_total. Ver la guía de paginación para la diferencia entre ambos totales.
bash
# Primera página
curl 'https://developers.fi-nova.com/api/v1/products?limit=25' \
-H 'Authorization: Bearer finova_sk_TU_SECRETO'
# Siguiente página (usar next_cursor de la respuesta anterior)
curl 'https://developers.fi-nova.com/api/v1/products?limit=25&starting_after=65a0c0f1234567890abcdef0' \
-H 'Authorization: Bearer finova_sk_TU_SECRETO'
# Filtrar por categoría (slug) + marca, con conteo del resultado filtrado
curl 'https://developers.fi-nova.com/api/v1/products?category=comida,bebidas&brand=acme&include_filtered_total=true' \
-H 'Authorization: Bearer finova_sk_TU_SECRETO'
# Incluir también los productos inactivos (no visibles en el sitio)
curl 'https://developers.fi-nova.com/api/v1/products?active=all' \
-H 'Authorization: Bearer finova_sk_TU_SECRETO'javascript
let cursor = null;
do {
const url = new URL('https://developers.fi-nova.com/api/v1/products');
url.searchParams.set('limit', '100');
if (cursor) url.searchParams.set('starting_after', cursor);
const res = await fetch(url, { headers: { Authorization: 'Bearer finova_sk_TU_SECRETO' } });
const body = await res.json();
// ...do something with body.data...
cursor = body.meta.has_more ? body.meta.next_cursor : null;
} while (cursor);ruby
cursor = nil
loop do
uri = URI('https://developers.fi-nova.com/api/v1/products')
uri.query = URI.encode_www_form({ limit: 100, starting_after: cursor }.compact)
req = Net::HTTP::Get.new(uri, 'Authorization' => 'Bearer finova_sk_TU_SECRETO')
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
body = JSON.parse(res.body)
# ...do something with body['data']...
break unless body.dig('meta', 'has_more')
cursor = body.dig('meta', 'next_cursor')
endObtener un producto
GET
/api/v1/products/:idread-productsDevuelve un único producto. El segmento :id acepta el id (ObjectId), el slug o el sku del producto — se resuelven en ese orden y siempre dentro de tu empresa. Útil para URLs amigables (/catalog/lentes-premium) sin tener que listar y filtrar. Ver Identificar un recurso.
bash
curl https://developers.fi-nova.com/api/v1/products/lentes-premium \
-H 'Authorization: Bearer finova_sk_TU_SECRETO'Si ningún identificador coincide (o pertenece a otra empresa), recibes 404 not_found. La respuesta incluye los campos de detalle (variants, option_types, components, inventory) además de los de la lista.
Crear un producto
POST
/api/v1/productscreate-productsCampos aceptados en data
name, slug, sku, description, product_type, is_active, product_category_id, brand_id, gtin, mpn, condition, prices_cents (mapa multi-moneda { <iso4217>: <centavos> }), inventory_enabled, inventory_mode, taxable, tax_rate_percent, tags, un objeto anidado seo (ver El objeto SEO), el array custom_properties (ver Campos personalizados), y las colecciones anidadas variants, option_types y components (ver Variantes, opciones y componentes).
Para asignar (o reasignar) la categoría de catálogo de un producto, manda product_category_id con el id de una categoría de la misma empresa (ver Categorías de producto). Mándalo vacío (""/null) para quitar la categoría. Un product_category_id que no exista o pertenezca a otra empresa devuelve 422 invalid_product_category. El campo category_name está denormalizado y se recalcula solo a partir de product_category_id; mandarlo no tiene efecto (se conserva en la respuesta solo por compatibilidad).
Los campos company_id, owner_id se asignan automáticamente a partir de la API key autenticada. brand_name y brand_slug se sincronizan automáticamente desde el Brand referenciado por brand_id. Las imágenes (binarios) no se mandan en el JSON: súbelas con los endpoints de imagen.
bash
curl -X POST https://developers.fi-nova.com/api/v1/products \
-H 'Authorization: Bearer finova_sk_TU_SECRETO' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: prod-import-row-42' \
-d '{
"data": {
"name": "Lentes oftálmicos premium",
"sku": "LEN-PREM-001",
"product_type": "simple",
"prices_cents": { "mxn": 450000, "usd": 25000 },
"is_active": true,
"taxable": true,
"tax_rate_percent": 16
}
}'javascript
await fetch('https://developers.fi-nova.com/api/v1/products', {
method: 'POST',
headers: {
Authorization: 'Bearer finova_sk_TU_SECRETO',
'Content-Type': 'application/json',
'Idempotency-Key': 'prod-import-row-42',
},
body: JSON.stringify({
data: {
name: 'Lentes oftálmicos premium',
sku: 'LEN-PREM-001',
product_type: 'simple',
prices_cents: { mxn: 450000, usd: 25000 },
is_active: true,
taxable: true,
tax_rate_percent: 16,
},
}),
});Actualizar un producto
PATCH
/api/v1/products/:idupdate-productsAcepta los mismos campos que create. Solo se actualizan los que mandes — los demás quedan intactos.
bash
curl -X PATCH https://developers.fi-nova.com/api/v1/products/65a0c0f1234567890abcdef0 \
-H 'Authorization: Bearer finova_sk_TU_SECRETO' \
-H 'Content-Type: application/json' \
-d '{ "data": { "prices_cents": { "mxn": 480000 } } }'Eliminar un producto
DELETE
/api/v1/products/:iddelete-productsBorra el producto. Devuelve 204 No Content. Los productos relacionados con ventas o movimientos de inventario existentes NO se pueden eliminar — recibirás 422 validation_failed y debes archivarlos desde la UI.
Imágenes
Las imágenes son binarios y se gestionan con endpoints dedicados (multipart/form-data), no en el JSON del producto. El campo del archivo se llama file. Tras subir, el producto reflejará image_url (y la entrada en images[]); las variantes reflejarán variants[].image_url.
Las URLs devueltas son firmadas y de corta duración — vuelve a leer el producto para refrescarlas.
Imagen principal del producto
POST
/api/v1/products/:id/imageupdate-productsDELETE
/api/v1/products/:id/imageupdate-productsbash
# Subir / reemplazar
curl -X POST https://developers.fi-nova.com/api/v1/products/65a0…/image \
-H 'Authorization: Bearer finova_sk_TU_SECRETO' \
-F 'file=@./lentes.jpg'
# Eliminar
curl -X DELETE https://developers.fi-nova.com/api/v1/products/65a0…/image \
-H 'Authorization: Bearer finova_sk_TU_SECRETO'Respuesta: { "data": { "id": "65a0…", "image_url": "https://…" } } (o image_url: null al eliminar).
Formatos aceptados: jpg, jpeg, png, webp, gif. Un archivo inválido devuelve 422 invalid_file; sin archivo, 422 missing_file.
Imagen de una variante
POST
/api/v1/products/:id/variants/:variant_id/imageupdate-productsDELETE
/api/v1/products/:id/variants/:variant_id/imageupdate-productsIgual que la imagen principal, pero apuntando a una variante existente. Si la variante no existe (o pertenece a otro producto), recibes 404 not_found. La imagen sigue a la variante por su id, así que se conserva al editar el producto.
bash
curl -X POST https://developers.fi-nova.com/api/v1/products/65a0…/variants/65c2…/image \
-H 'Authorization: Bearer finova_sk_TU_SECRETO' \
-F 'file=@./variante-roja.jpg'Respuesta: { "data": { "id": "65a0…", "variant_id": "65c2…", "image_url": "https://…" } }.
Errores específicos
error | Cuándo |
|---|---|
validation_failed (422) | El payload no pasa validaciones (precio negativo, SKU duplicado, etc.). |
not_found (404) | El id (o variant_id) no existe o pertenece a otra empresa. |
invalid_product_category (422) | El product_category_id no existe o pertenece a otra empresa. |
missing_file (422) | Endpoint de imagen sin archivo file. |
invalid_file (422) | Imagen con formato no permitido. |
insufficient_scope (403) | La API key no tiene el scope requerido por el endpoint. |