ByDomuDevelopers

Paginação, idempotência e limites

Percorra listas por página ou cursor, tente de novo sem duplicar e trate o 429.

GuiaREST

Percorra listas por página ou cursor

Todas as listas aceitam page (desde 1) e pageSize, e trazem meta.total e meta.totalPages. Contatos, negócios e tarefas aceitam também cursor: passe o meta.nextCursor da resposta anterior até meta.hasMore ser false. Com includeTotal=false o total não é contado (chega null) e fica mais rápido. O pageSize máximo é 500 em contatos, negócios, tarefas e compromissos, e 48 em imóveis.

// Contactos, negocios y tareas: por cursor, sin contar el total (lo más rápido).
async function* all(path, params = {}) {
  let cursor;
  do {
    const qs = new URLSearchParams({ ...params, pageSize: "500", includeTotal: "false",
                                     ...(cursor ? { cursor } : {}) });
    const res = await api(`${path}?${qs}`);
    const { data, meta } = await res.json();
    yield* data;
    cursor = meta.hasMore ? meta.nextCursor : undefined;
  } while (cursor);
}

for await (const contact of all("/contacts", { status: "NEW" })) { /* … */ }

Em imóveis o cursor é o id do último recebido (não há nextCursor) e não envie includeTotal=false: ali «false» conta como verdadeiro.

Tente de novo sem duplicar

Envie Idempotency-Key em POST, PATCH, PUT e DELETE: de 8 a 128 caracteres (letras, números e . _ : -), única por operação na sua imobiliária. Durante 24 horas, a mesma chave com o mesmo método, rota e corpo devolve a resposta guardada sem repetir a mudança. Respostas 5xx não são guardadas: você pode tentar de novo com a mesma chave.

cURL
curl -X POST https://api.bydomu.com/v1/contacts \
  -H "X-API-Key: $BYDOMU_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: crm-sync:contact:u_123" \
  -d '{"firstName":"Laura","phone":"+573000000001"}'

# 1.ª vez   → 201 · Idempotency-Status: created
# repetida  → 201 · Idempotency-Status: replayed (la misma respuesta, sin crear otro)
# otro body → 409 IDEMPOTENCY_CONFLICT
# en curso  → 409 IDEMPOTENCY_IN_PROGRESS

Derive a chave do seu próprio id (crm-sync:contact:u_123) e não da hora: assim uma nova tentativa, mesmo de outro processo, cai na mesma operação.

Limites por minuto e o 429

Cada chave tem dois baldes por minuto: todas as requisições e, à parte, as escritas. O suporte pode aprovar mais. Há também um teto de escritas por organização (TENANT_RATE_LIMITED).

ChaveLimite
Chave publicável300/min · 60 escritas
Chave secreta60/min · 30 escritas
Chave anterior aos níveis1000/min
O que dizCabeçalho
Teto por minuto da chaveX-RateLimit-Limit
O que resta neste minutoX-RateLimit-Remaining
Segundos até reiniciarX-RateLimit-Reset
Teto de escritas (num 429 de escrita)X-RateLimit-Write-Limit
Escritas da organização todaX-Tenant-RateLimit-Remaining
Segundos para esperar (no 429)Retry-After

Um cliente que tenta de novo direito

Com 429 espere os segundos de Retry-After; com 5xx, espera crescente com um pouco de aleatoriedade. Nunca repita um 4xx diferente de 429: não vai mudar.

import { randomUUID } from "node:crypto";

// Reintenta 429 y 5xx con la MISMA Idempotency-Key: nunca duplica.
export async function api(path, { method = "GET", body, idempotencyKey } = {}) {
  const key = idempotencyKey ?? (method === "GET" ? undefined : randomUUID());
  for (let attempt = 1; ; attempt++) {
    const res = await fetch(`https://api.bydomu.com/v1${path}`, {
      method,
      headers: {
        "X-API-Key": process.env.BYDOMU_API_KEY,
        ...(body ? { "Content-Type": "application/json" } : {}),
        ...(key ? { "Idempotency-Key": key } : {}),
      },
      ...(body ? { body: JSON.stringify(body) } : {}),
    });
    const retryable = res.status === 429 || res.status >= 500;
    if (!retryable || attempt === 5) return res;
    const retryAfter = Number(res.headers.get("Retry-After"));
    const wait = retryAfter > 0 ? retryAfter * 1000 : Math.min(30_000, 500 * 2 ** attempt);
    await new Promise((r) => setTimeout(r, wait + Math.random() * 250));
  }
}