Pagination
Parcourez les longues listes avec un curseur : stable même si des enregistrements sont créés pendant que vous paginez.
Quelle pagination pour quelle liste
| Endpoint | Curseur | Offset | Ordre | meta |
|---|---|---|---|---|
GET /api/v1/cotizaciones |
Oui | Oui | Le plus récent en premier | limit, offset, total, next_cursor |
GET /api/v1/clientes |
Oui | Oui | Par empresa (de A à Z) |
limit, offset, total, next_cursor |
GET /api/v1/productos |
Oui | Oui | Actifs d’abord, puis par nombre |
limit, offset, total, next_cursor |
GET /api/v1/facturas |
Oui | Non | La plus récente en premier | next_cursor |
GET /api/v1/events |
Oui | Non | Le plus récent en premier | next_cursor |
GET /api/v1/webhooks |
Non | Non | Le plus récent en premier | Pas de meta : renvoie tous les endpoints de la clé. |
GET /api/v1/cobranza |
Non | Non | — | Pas de meta : renvoie l’ensemble des créances. |
Dans toutes les listes paginées, limit va de 1 à 200 et vaut 50 si vous ne l’envoyez pas.
Utilisez le curseur
Demandez la première page sans curseur. Si meta.next_cursor contient une valeur, répétez la même requête, avec les mêmes filtres, en ajoutant cursor=<next_cursor>. Lorsqu’il vaut null, il n’y a plus rien.
curl "https://cordhq.app/api/v1/clientes?limit=2" \
-H "Authorization: Bearer sk_test_..."
{
"data": [
{ "id": "0e6f2b8a-91c4-4d7e-b3a5-6c2d8f1e4a90", "empresa": "Aceros Monterrey", "email": "compras@acerosmty.mx" },
{ "id": "7a2d4c19-5e8b-4f3a-a1d6-9c0e2b4f6d81", "empresa": "Bodega Central", "email": "" }
],
"meta": {
"limit": 2,
"offset": 0,
"total": 142,
"next_cursor": "WyJCb2RlZ2EgQ2VudHJhbCIsIjdhMmQ0YzE5LTVlOGItNGYzYS1hMWQ2LTljMGUyYjRmNmQ4MSJd"
}
}
curl "https://cordhq.app/api/v1/clientes?limit=2&cursor=WyJCb2RlZ2EgQ2VudHJhbCIsIjdhMmQ0YzE5LTVlOGItNGYzYS1hMWQ2LTljMGUyYjRmNmQ4MSJd" \
-H "Authorization: Bearer sk_test_..."
L’exemple réduit chaque client à trois champs ; la réponse réelle contient l’objet complet.
Le curseur est un jeton opaque : ne l’interprétez pas, ne le construisez pas et ne le conservez pas pour une autre liste. Renvoyez-le tel quel, encodé pour l’URL (les exemples ci-dessous le font pour vous). Avec cursor, l’offset est ignoré et meta.offset vaut null.
Pourquoi un curseur plutôt qu’un offset
Avec offset, un devis créé pendant que vous parcourez la liste décale tout d’une position : la page suivante répète un enregistrement, et si l’un d’eux est supprimé, vous en sautez un autre sans vous en rendre compte. Le curseur pointe vers la dernière ligne que vous avez vue : la page suivante commence donc exactement après, qu’il y ait eu des écritures ou non.
offset fonctionne toujours sur les devis, les clients et les produits, par compatibilité. meta.total compte tous les enregistrements correspondant au filtre : il sert à afficher « 142 clients », pas à décider quand s’arrêter. Pour cela, utilisez next_cursor.
Parcourir une liste complète
#!/usr/bin/env bash
# Nécessite jq. Affiche l’id et l’entreprise de tous les clients.
set -euo pipefail
args=(-sSG "https://cordhq.app/api/v1/clientes" -d limit=200 -H "Authorization: Bearer $CORD_API_KEY")
cursor=""
while :; do
if [ -n "$cursor" ]; then
page=$(curl "${args[@]}" --data-urlencode "cursor=$cursor")
else
page=$(curl "${args[@]}")
fi
echo "$page" | jq -r '.data[] | [.id, .empresa] | @tsv'
cursor=$(echo "$page" | jq -r '.meta.next_cursor // empty')
[ -z "$cursor" ] && break
doneimport { Cord } from '@flouviahq/node';
const cord = new Cord(process.env.CORD_API_KEY);
// listAll() demande des pages de 200 et suit next_cursor pour vous.
for await (const cliente of cord.clients.listAll()) {
console.log(cliente.id, cliente.empresa);
}
// Avec des filtres :
for await (const factura of cord.invoices.listAll({ estado: 'open' })) {
console.log(factura.numero, factura.saldo, factura.moneda);
}const base = 'https://cordhq.app/api/v1/events';
let cursor = null;
do {
const url = new URL(base);
url.searchParams.set('limit', '200');
url.searchParams.set('type', 'quote.paid');
if (cursor) url.searchParams.set('cursor', cursor);
const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.CORD_API_KEY}` } });
if (!res.ok) throw new Error(`${res.status} ${(await res.json()).code}`);
const page = await res.json();
for (const event of page.data) console.log(event.created_at, event.type, event.object_id);
cursor = page.meta.next_cursor;
} while (cursor);listAll() existe sur quotes, clients, products, invoices et events, et pagine toujours par curseur depuis la version 1.1.0 du SDK. Voir SDK Node.
Erreurs
| Situation | Réponse |
|---|---|
Un cursor qui ne provient pas des devis, des clients ou des produits |
400, code: "invalid_cursor", error: "El cursor no es válido. Usa el next_cursor de la respuesta anterior." |
Un cursor qui ne provient pas des événements |
400, code: "invalid_request", error: "cursor inválido" |
limit hors limites |
Ce n’est pas une erreur : il est ramené à 1 ou à 200. |
{
"error": "El cursor no es válido. Usa el next_cursor de la respuesta anterior.",
"code": "invalid_cursor",
"request_id": "req_5d2b8f1a4c7e0b3d6a9f2c5e",
"doc_url": "https://docs.cordhq.app/docs/desarrolladores/esenciales/errores#invalid_cursor"
}
Un curseur ne se transmet ni d’une liste à l’autre ni d’un filtre à l’autre : si vous changez status, type ou tout autre filtre, recommencez sans curseur.