Paginació
Paginació per cursor amb starting_after i ending_before. Sense ?page=, semàntica a l'estil Stripe.
Tots els endpoints de llistat de l'API pública usen paginació per cursor.
Mateixa semàntica que Stripe / Linear: pagines per un identificador opac (l'id
del recurs), no per número de pàgina. Això garanteix resultats estables
fins i tot quan es creen nous recursos durant la iteració.
Paràmetres
| Paràmetre | Tipus | Per defecte | Rang | Descripció |
|---|---|---|---|---|
limit | integer | 25 | 1–100 | Nombre d'elements per pàgina. |
starting_after | string | null | id (UUID v7) | Retorna els elements creats després del recurs el id del qual es passa. |
ending_before | string | null | id (UUID v7) | Retorna els elements creats abans del recurs el id del qual es passa. |
sort | string | -created | camp per recurs | Camp d'ordre; prefix - per a descendent (p.ex. -total). Vegeu Ordre. |
starting_after i ending_before són mútuament excloents. Enviar
tots dos en la mateixa petició respon 422 amb un embolcall d'error
invalid_request_error.
Forma de la resposta
{
"data": [
{ "id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a0b", "...": "..." },
{ "id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a0c", "...": "..." }
],
"has_more": true,
"next_cursor": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a0c"
}data: array de fins alimitelements, ordenats peridDESC (equivalent acreated_atDESC perquè usem UUID v7).has_more:truesi hi ha més elements abans del primer dedata(més nous) en paginar ambstarting_after, o després de l'últim en paginar ambending_before.next_cursor:idde l'últim element dedata. Passa'l com astarting_afteren la petició següent per avançar.
Quan no hi ha més elements, has_more és false i next_cursor és
null.
Iterar tots els resultats
import os, requests
def iterate(endpoint):
cursor = None
while True:
params = {'limit': 100}
if cursor:
params['starting_after'] = cursor
resp = requests.get(
f'https://api.factuarea.com/v1/{endpoint}',
params=params,
headers={'Authorization': f"Bearer {os.environ['FACTUAREA_API_KEY']}"},
)
resp.raise_for_status()
body = resp.json()
yield from body['data']
if not body['has_more']:
break
cursor = body['next_cursor']
for invoice in iterate('invoices'):
print(invoice['id'], invoice['number'])async function* iterate(endpoint) {
let cursor = null;
while (true) {
const url = new URL(`https://api.factuarea.com/v1/${endpoint}`);
url.searchParams.set('limit', '100');
if (cursor) url.searchParams.set('starting_after', cursor);
const res = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.FACTUAREA_API_KEY}` },
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const body = await res.json();
for (const item of body.data) yield item;
if (!body.has_more) break;
cursor = body.next_cursor;
}
}
for await (const invoice of iterate('invoices')) {
console.log(invoice.id, invoice.number);
}cursor=""
while : ; do
if [ -z "$cursor" ]; then
url="https://api.factuarea.com/v1/invoices?limit=100"
else
url="https://api.factuarea.com/v1/invoices?limit=100&starting_after=$cursor"
fi
resp=$(curl -s -H "Authorization: Bearer $FACTUAREA_API_KEY" "$url")
echo "$resp" | jq -c '.data[]'
has_more=$(echo "$resp" | jq -r '.has_more')
cursor=$(echo "$resp" | jq -r '.next_cursor')
[ "$has_more" = "true" ] || break
doneIterar amb l'SDK oficial
Els SDK de TypeScript i PHP amaguen el cursor completament: els mètodes de llistat retornen un iterador que recorre totes les pàgines per tu.
const page = await factuarea.invoices.list({ status: "paid", limit: 50 });
// iterate every item across every page — cursors handled internally
for await (const invoice of page) {
console.log(invoice.id, invoice.number);
}
// or walk page by page
page.data; // items on this page
page.hasMore; // boolean
page.nextCursor; // opaque cursor or null
const next = await page.getNextPage(); // Page | null
const all = await page.toArray(); // collect everythinguse Factuarea\Sdk\Custom\Pagination\PageIterator;
use Factuarea\Sdk\Models\Operations\PublicApiV1InvoicesListRequest;
$pages = new PageIterator(
fn (?string $cursor) => $factuarea->invoices->publicApiV1InvoicesList(
new PublicApiV1InvoicesListRequest(startingAfter: $cursor),
)->rawResponse,
);
foreach ($pages->items() as $invoice) {
echo $invoice['id'], PHP_EOL;
}Consulta SDKs › Paginar amb l'SDK per veure la superfície completa.
Ordre
Passa ?sort=<camp> per ordenar un llistat. Un camp tot sol ordena de
manera ascendent; el prefix - ordena de manera descendent
(p.ex. ?sort=-total). Si s'omet, el valor per defecte és -created
— equivalent a id DESC, un ordre total i estable perquè usem UUID v7
(que codifica una marca de temps en els 48 bits alts, de manera que "els
creats més recentment primer" no necessita cap columna created_at
addicional).
El cursor (starting_after / ending_before) continua funcionant amb
l'ordre que triïs: el camp escollit és el criteri principal i el id del
recurs és un criteri secundari estable, de manera que la paginació es
manté determinista fins i tot quan diverses files comparteixen el mateix
valor (a l'estil de Stripe).
Els camps sort permesos estan acotats per recurs — enviar un camp no
suportat respon 422 amb un embolcall d'error invalid_request_error:
| Recurs | Camps sort permesos |
|---|---|
invoices | created, total, number |
quotes | created, total, number, valid_until |
proformas | created, total, number, valid_until |
delivery_notes | created, number, delivery_date |
purchase_invoices | created, total, issued_on, due_on |
recurring_invoices | created, next_run_at |
# Factures, total més alt primer
curl -G https://api.factuarea.com/v1/invoices \
-H "Authorization: Bearer $FACTUAREA_API_KEY" \
--data-urlencode "sort=-total"
# Pressupostos per data de validesa, els que vencen abans primer
curl -G https://api.factuarea.com/v1/quotes \
-H "Authorization: Bearer $FACTUAREA_API_KEY" \
--data-urlencode "sort=valid_until"Per què no ?page=?
Les pàgines numèriques tenen problemes quan el conjunt de dades canvia durant la iteració:
- Crear un recurs entre pàgines → duplica files.
- Eliminar un recurs entre pàgines → omet files.
COUNT(*)és costós passades unes quantes milers de files.
La paginació per cursor amb UUID v7 elimina tots dos: el cursor apunta a una posició estable en el temps, no a un desplaçament variable.
Per això no hi ha cap paràmetre de desplaçament ?page=N — l'única manera de paginar
per una llista és el cursor starting_after / ending_before. Un
valor de cursor invàlid respon 422 amb un embolcall d'error
invalid_request_error (code: parameter_invalid_cursor):
{
"error": {
"type": "invalid_request_error",
"code": "parameter_invalid_cursor",
"message": "The provided cursor is not a valid resource id.",
"param": "starting_after",
"request_id": "req_..."
}
}Cas d'ús: obtenir només resultats nous
Si la teva integració fa polling cada N minuts, desa el next_cursor (l'id
més recent que has vist) entre cada sondeig. A la passada següent usa
ending_before=<saved_cursor> per obtenir només els elements més nous que aquell
punt.
last_seen = load_last_cursor() # resource id stored in your DB
resp = requests.get(
'https://api.factuarea.com/v1/invoices',
params={'limit': 100, 'ending_before': last_seen} if last_seen else {'limit': 100},
headers={'Authorization': f"Bearer {API_KEY}"},
)
new_invoices = resp.json()['data']
if new_invoices:
save_last_cursor(new_invoices[0]['id']) # the newest oneErrors i límits de peticions
Formes d'error JSON-RPC mapejades des del contracte v1, la taula completa de codis i throttling per token / per pla amb Retry-After.
Etiquetes i camps personalitzats
Classifica documents amb tags i adjunta custom_fields tipats. Filtra llistats per tag. En què es diferencien de metadata.