uindocs
API

Paginación y filtros

Los endpoints de listado comparten el mismo contrato — page y limit son obligatorios y los filtros van como JSON en el query string

Todos los endpoints que terminan en /paginated comparten la misma forma de pedir y de responder.

Pedir una página

GET /accounts/paginated?page=1&limit=25
Authorization: Bearer <YOUR_API_KEY>

page y limit son obligatorios. No tienen valor por defecto: si los omitís, la request falla con 400. page arranca en 1, no en 0.

La respuesta

{
  "rows": [ /* … los items de esta página … */ ],
  "next": true,
  "previous": false,
  "page": 1,
  "perPage": 25,
  "totalPages": 12,
  "totalRows": 287
}
CampoDescripción
rowsLos items de esta página.
next / previousSi hay página siguiente / anterior. Útil para paginar sin calcular.
pageLa página que estás viendo.
perPageEl limit que aplicaste.
totalPagesCantidad total de páginas.
totalRowsCantidad total de items que matchean, ignorando la paginación.

Para recorrer todo, iterá mientras next sea true en vez de comparar page con totalPages — te ahorra un off-by-one.

let page = 1;
const all = [];

while (true) {
  const res = await fetch(`${BASE}/accounts/paginated?page=${page}&limit=100`, { headers });
  const body = await res.json();
  all.push(...body.rows);
  if (!body.next) break;
  page++;
}

Filtros

Los listados aceptan un parámetro filters que es un objeto JSON serializado como string dentro del query param. No es un set de query params sueltos.

{
  "filters": [
    { "field": "email", "value": "user@example.com" }
  ],
  "matchAll": true,
  "includeDeleted": false
}

Serializado y URL-encodeado:

GET /accounts/paginated?page=1&limit=25&filters=%7B%22filters%22%3A%5B%7B%22field%22%3A%22email%22%2C%22value%22%3A%22user%40example.com%22%7D%5D%7D

En código, dejá que lo arme el cliente HTTP:

const filters = JSON.stringify({
  filters: [{ field: 'email', value: 'user@example.com' }],
  matchAll: true,
});

const url = `${BASE}/accounts/paginated?${new URLSearchParams({
  page: '1',
  limit: '25',
  filters,
})}`;

Campos del objeto

CampoDefaultDescripción
filters[]Array de condiciones { field, value }.
matchAlltruetrue combina las condiciones con AND; false, con OR.
includeDeletedfalseSi true, incluye registros borrados lógicamente.

Cómo se compara cada valor

La semántica depende del tipo del campo y del valor que mandes, no es siempre igualdad:

Valor que mandásSobre una columna de textoSobre cualquier otra columna
Un stringCoincidencia parcial, case-insensitive (ilike %valor%)Igualdad exacta
Un número, boolean o fecha—Igualdad exacta
nullIS NULLIS NULL
Un arrayCualquiera de esos valores (IN), exactosCualquiera de esos valores (IN)

Sobre campos de texto, un string busca por subcadena. { "field": "name", "value": "Juan" } también trae "Juana" y "San Juan". Si necesitás una coincidencia exacta sobre texto, mandá el valor dentro de un array: { "field": "name", "value": ["Juan"] }.

No hay operadores de rango (>, <, between) ni negación.

Campos y valores válidos

Cada endpoint declara qué campos son filtrables. Si mandás uno que no lo es, la respuesta es 400 y el mensaje te lista los campos permitidos:

{
  "statusCode": 400,
  "message": "Invalid filter field \"nickname\". Allowed fields: id, email, name, externalId, createdAt.",
  "code": "BAD_REQUEST"
}

Lo mismo con los campos de tipo enum, que además validan el valor:

{
  "statusCode": 400,
  "message": "Invalid value \"ACTIVO\" for filter \"status\". Allowed values: ACTIVE, PENDING, BLOCKED.",
  "code": "BAD_REQUEST"
}

Es la forma más rápida de descubrir qué acepta un endpoint: mandá un campo inventado y leé la lista que te devuelve.

Filtros en respuestas sin paginación

El feed (GET /v1/feed/account/{accountId}) y las recompensas ganadas (GET /v1/gamification/missions/my-rewards/{accountId}) aceptan el mismo filters, pero devuelven un array completo: page y limit no aplican.

EndpointCampos filtrables
Feedstatus (ACTIVE, COMPLETED, EXPIRED), type (MISSION, CHALLENGE, STREAK, ROUTINE), campaignId
Recompensas ganadascampaignId, rewardType (PRODUCTS, FRAGMENTS, POINTS, CHEST), status (DELIVERED, PENDING_STOCK)

Dos diferencias con los listados paginados:

  • La comparación es siempre exacta. Son ids y enums, así que un string no busca por subcadena.
  • includeDeleted no aplica.

Lo que sí se mantiene igual:

  • matchAll funciona como en los listados: true combina las condiciones con AND, false con OR.
  • value: null matchea ausencia del campo. Es la forma de pedir, por ejemplo, los ítems del feed que no pertenecen a ninguna campaña.

El mensaje del 400 es el mismo que en el resto de la API, pero el code no: acá viene "INTERNAL_SERVER_ERROR" con "statusCode": 400. Si rameás por code como indica Errores, tratá explícitamente este caso por statusCode, para no reintentar con backoff un filtro mal escrito.

Errores

StatusCausa
400Falta page o limit, o no son enteros (solo en los listados paginados).
400filters no es un JSON válido, o filters.filters no es un array.
400Un field no es filtrable en ese endpoint, o un valor no pertenece al enum de la columna.

El detalle del formato está en Errores.

On this page