---
name: ecomiq-admin-api
description: Integra un sistema con Ecomiq Admin API (https://api.ecomiq.pe/api) — tokens OAuth, productos y variantes, inventario, pedidos, clientes y paginación. Úsalo cuando haya que conectar un ERP, backend o script con una tienda Ecomiq, o cuando aparezcan endpoints /v1/..., errores 401/403/409/429 o tokens de auth.ecomiq.pe.
---

# Ecomiq Admin API

Admin API opera una tienda Ecomiq desde un servidor: catálogo, inventario,
clientes, pedidos, envíos y pagos.

- URL base: `https://api.ecomiq.pe/api`. Las rutas empiezan en `/v1/`.
- Referencia: https://docs.ecomiq.pe/es/docs/admin
- OpenAPI: https://docs.ecomiq.pe/openapi/admin.openapi.json
- Índice para IA: https://docs.ecomiq.pe/llms.txt

Antes de usar una operación que no está aquí, búscala en el OpenAPI. Nunca
inventes rutas, campos ni valores de enum.

## Autenticación

Las credenciales se emiten en `https://auth.ecomiq.pe`. Hay dos flujos.

### Un servidor actúa como la aplicación (`client_credentials`)

La persona crea la aplicación en el panel de la tienda (**Configuración →
Desarrolladores → Crear app**) con los permisos que necesita la integración, y
te pasa `client_id` y `client_secret` en variables de entorno
(`ECOMIQ_CLIENT_ID`, `ECOMIQ_CLIENT_SECRET`), nunca por el chat. El secreto
vive solo en el servidor: no lo escribas en código, logs ni archivos que se
suban.

```bash
curl -X POST https://auth.ecomiq.pe/connect/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id=$ECOMIQ_CLIENT_ID" \
  -d "client_secret=$ECOMIQ_CLIENT_SECRET" \
  -d "scope=api_admin"
```

- La respuesta trae `access_token` y `expires_in` (segundos). No hay refresh
  token: pide otro antes de que venza y guárdalo en caché con un minuto de
  margen.
- Coordina la renovación si varios procesos comparten credenciales.

### En nombre de una persona (`authorization_code` + PKCE)

1. Genera `code_verifier` y `code_challenge` (solo `S256`; PKCE es obligatorio)
   y un `state` aleatorio.
2. Redirige a `https://auth.ecomiq.pe/connect/authorize` con `response_type=code`,
   `client_id`, `redirect_uri` (idéntica a una registrada), `scope` (por ejemplo
   `api_admin offline_access`), `state`, `code_challenge` y
   `code_challenge_method=S256`.
3. La persona inicia sesión, elige la tienda y acepta. Vuelve con `code` y
   `state`; compara `state`.
4. Canjea en `/connect/token` con `grant_type=authorization_code`, `code`,
   `redirect_uri`, `code_verifier`, `client_id` y `client_secret` (los clientes
   públicos lo omiten). El código vence a los 5 minutos y sirve una vez.
5. El access token dura 1 hora y el refresh token 14 días (requiere el scope
   `offline_access` y el grant `refresh_token`). Cada renovación devuelve un
   refresh token nuevo: guarda siempre el último.

Una aplicación creada en el panel solo se autoriza en tiendas de la
organización que la creó.

### Usar el token

```bash
curl "https://api.ecomiq.pe/api/v1/catalog/products?limit=25" \
  -H "Authorization: Bearer $ECOMIQ_TOKEN"
```

- La tienda sale del token. `x-Store-Id` es opcional y, si lo envías, debe
  coincidir; si no, responde `403`.
- Una API key (`POST /v1/api-keys`) no sirve como token: Admin, Storefront y
  Auth no la aceptan.
- Nunca registres tokens, secretos ni respuestas OAuth completas.

## Convenciones

- Los IDs son strings opacos: guárdalos y compáralos tal cual, sin convertirlos
  a número.
- JSON en camelCase.
- Paginación: cada operación declara su estrategia. Por cursor envía `limit` y
  `cursor`; la respuesta trae `data`, `hasNextPage` y `nextCursor`. No mezcles
  `page`/`pageSize` con `cursor`.
- Errores: Problem Details con `code` estable (por ejemplo
  `Catalog.ProductNotFound`). `/connect/token` responde errores OAuth
  (`invalid_client`, `invalid_grant`) sin `code`.

| Estado | Qué hacer |
| --- | --- |
| `400` | Corrige la petición; no la repitas igual. |
| `401` | Credencial inválida o vencida: pide otro token. |
| `403` | Falta permiso, scope o contexto de tienda. |
| `404` | Revisa el ID y la tienda antes de asumir que no existe. |
| `409` | Conflicto de estado o versión: vuelve a leer y reintenta. |
| `429` | Respeta `Retry-After` y reintenta con espera creciente. |
| `5xx` | Reintenta con espera creciente solo si la operación es idempotente. |

## Tareas comunes

### Crear un producto

`POST /v1/catalog/products`. La respuesta `201` trae el producto con sus
variantes: guarda el `id` de cada variante junto a su SKU. Campos completos en
https://docs.ecomiq.pe/es/docs/admin/catalog/products/create-a-product

### Leer y ajustar stock

```bash
curl -X POST https://api.ecomiq.pe/api/v1/inventory/levels \
  -H "Authorization: Bearer $ECOMIQ_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "variantIds": ["7204558912004325377"] }'
```

Cada nivel trae la ubicación, el stock y su `version`. Para ajustar:

```bash
curl -X POST https://api.ecomiq.pe/api/v1/inventory/adjust \
  -H "Authorization: Bearer $ECOMIQ_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "variantId": "7204558912004325377",
    "locationId": "7204558912004325378",
    "target": "onHand",
    "mode": "adjust",
    "quantity": 5,
    "reason": "restock",
    "expectedVersion": 3
  }'
```

- `mode: "adjust"` suma la cantidad; `mode: "set"` la fija.
- `expectedVersion` es la `version` leída, o `0` si la variante aún no tiene
  nivel en esa ubicación. Si responde `409`, vuelve a leer el nivel.

### Leer pedidos

- `GET /v1/orders?limit=25` lista pedidos; filtra con `status`, `state`,
  `tags`, `dateFrom` y `dateTo`.
- `GET /v1/orders/{orderId}` trae el pedido completo con `items`.
- Para enterarte de pedidos nuevos, registra un webhook `order.created`
  (`POST /v1/webhooks/endpoints` con `url` y `eventTypes`) en lugar de consultar
  en bucle.

### Cargar en bloque

- Hasta 100 registros en JSON: `POST /v1/catalog/products/batch`,
  `/v1/inventory/batch`, `/v1/orders/batch` o `/v1/shipping/rates/batch`.
  Responden `200` con `results[]` en el orden recibido; revisa los `failed`.
- Un archivo CSV, TSV, XLSX o JSONL: `POST /v1/{recurso}/imports` con el
  archivo en el cuerpo y su `Content-Type`. Responde `202` y `Location`
  apunta a `/v1/operations/{id}`.
- Envía `Idempotency-Key` y repítela al reintentar el mismo archivo.
  `?validateOnly=true` valida sin escribir.
- Consulta `GET /v1/operations/{id}` mientras `status` sea `pending` o
  `running`, o espera el webhook `operation.finished`. `errorsUrl` es un CSV
  con `sheet,row,column,code,message`.
- La plantilla es `POST /v1/{recurso}/exports` con
  `{ "format": "xlsx", "template": true }`.
- Guía: https://docs.ecomiq.pe/es/docs/guides/catalog/product-import

## Límites de uso

- Usa el mayor tamaño de página que permita la operación.
- Limita la concurrencia con una cola local.
- Cachea lo que cambia poco (categorías, zonas).
- Ante `429`, espera lo que indique `Retry-After`.
