Percorrer uma lista
- Faça a primeira chamada sem
cursor. - Enquanto
has_morefortrue, repita a chamada enviando onext_cursorrecebido no parâmetrocursor, com os mesmos filtros e o mesmosort. - Quando
has_moreforfalse,next_cursorvemnulle a leitura terminou.
GET /v1/orders?limit=100&status=PAID → has_more: true, next_cursor: "eyJ2..."
GET /v1/orders?limit=100&status=PAID&cursor=eyJ2... → has_more: true, next_cursor: "eyJ3..."
GET /v1/orders?limit=100&status=PAID&cursor=eyJ3... → has_more: false, next_cursor: null
Parâmetros e campos
| Nome | Onde | Descrição |
|---|---|---|
limit | query | Itens por página, de 1 a 100. Padrão: 20. Fora disso, 400 com param: "limit". |
cursor | query | O next_cursor da página anterior. Omita na primeira chamada. |
has_more | resposta | true quando há mais itens depois desta página. |
next_cursor | resposta | Cursor da próxima página, ou null na última. |
total, total_count). Para saber quantos itens existem,
percorra a lista. Uma lista vazia vem com data: [], has_more: false e next_cursor: null.
Regras do cursor
- É opaco. Não interprete o conteúdo nem monte um cursor: envie exatamente o que recebeu.
- Vale só para a mesma conta, os mesmos filtros e o mesmo
sortda chamada que o gerou. Cursor alterado, de outra conta ou enviado com filtros ousortdiferentes recebe400invalid_requestcomparam: "cursor". Nesse caso, recomece a listagem semcursor. - O
limitpode mudar entre páginas; os filtros e osort, não.
Garantias de cada ordem
O que acontece com pedidos criados ou alterados enquanto você percorre as páginas depende dosort:
sort | Garantia durante a leitura |
|---|---|
created_at_desc (padrão) | Não repete nem pula os pedidos que já existiam na primeira chamada. Pedidos criados depois dela não aparecem nas páginas seguintes. |
updated_at_asc | Nenhum pedido que casava com os filtros na primeira chamada é pulado. Um pedido atualizado durante a leitura ganha updated_at novo e aparece de novo numa página seguinte, já com os dados novos. Um pedido criado durante a leitura aparece no fim. Pedidos com updated_at null vêm depois de todos os outros. |
updated_at_asc, o mesmo pedido pode vir mais de uma vez: grave por id, substituindo a
versão anterior. Essa é a base da sincronização de pedidos.
# Requer jq. Percorre todas as páginas dos pedidos pagos, 100 por vez.
cursor=""
while :; do
page=$(curl -sS -G "https://api.zouti.com.br/v1/orders" \
-H "Authorization: Bearer $ZOUTI_API_KEY" \
--data-urlencode "limit=100" \
--data-urlencode "status=PAID" \
${cursor:+--data-urlencode "cursor=$cursor"})
echo "$page" | jq -c '.data[] | {id, status, amount_total}'
[ "$(echo "$page" | jq -r '.has_more')" = "true" ] || break
cursor=$(echo "$page" | jq -r '.next_cursor')
done
// Node.js 18+ (fetch nativo). Percorre todas as páginas dos pedidos pagos, 100 por vez.
const API_KEY = process.env.ZOUTI_API_KEY;
const BASE_URL = 'https://api.zouti.com.br';
const FILTERS = { limit: '100', status: 'PAID' }; // os mesmos em todas as páginas
async function listPage(cursor) {
const query = new URLSearchParams(FILTERS);
if (cursor) query.set('cursor', cursor);
const response = await fetch(`${BASE_URL}/v1/orders?${query}`, {
headers: { Authorization: `Bearer ${API_KEY}` },
});
const body = await response.json();
if (!response.ok) {
throw new Error(`${body.error.code}: ${body.error.message} (${body.error.request_id})`);
}
return body;
}
const orders = [];
let cursor = null;
do {
const page = await listPage(cursor);
orders.push(...page.data);
cursor = page.has_more ? page.next_cursor : null;
} while (cursor);
console.log(`${orders.length} pedidos lidos`);
# Python 3 com requests. Percorre todas as páginas dos pedidos pagos, 100 por vez.
import os
import requests
API_KEY = os.environ["ZOUTI_API_KEY"]
BASE_URL = "https://api.zouti.com.br"
FILTERS = {"limit": 100, "status": "PAID"} # os mesmos em todas as páginas
def list_page(cursor=None):
params = dict(FILTERS)
if cursor:
params["cursor"] = cursor
response = requests.get(
f"{BASE_URL}/v1/orders",
params=params,
headers={"Authorization": f"Bearer {API_KEY}"},
timeout=30,
)
body = response.json()
if not response.ok:
error = body["error"]
raise RuntimeError(f"{error['code']}: {error['message']} ({error['request_id']})")
return body
orders = []
cursor = None
while True:
page = list_page(cursor)
orders.extend(page["data"])
if not page["has_more"]:
break
cursor = page["next_cursor"]
print(f"{len(orders)} pedidos lidos")
<?php
// PHP 7.4+ com a extensão curl. Percorre todas as páginas dos pedidos pagos, 100 por vez.
const BASE_URL = 'https://api.zouti.com.br';
const FILTERS = ['limit' => 100, 'status' => 'PAID']; // os mesmos em todas as páginas
function list_page(?string $cursor): array
{
$query = FILTERS;
if ($cursor !== null) {
$query['cursor'] = $cursor;
}
$ch = curl_init(BASE_URL . '/v1/orders?' . http_build_query($query));
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('ZOUTI_API_KEY')],
]);
$raw = curl_exec($ch);
if ($raw === false) {
throw new RuntimeException(curl_error($ch));
}
$body = json_decode($raw, true);
if (curl_getinfo($ch, CURLINFO_RESPONSE_CODE) !== 200) {
$error = $body['error'];
throw new RuntimeException("{$error['code']}: {$error['message']} ({$error['request_id']})");
}
return $body;
}
$orders = [];
$cursor = null;
do {
$page = list_page($cursor);
array_push($orders, ...$page['data']);
$cursor = $page['has_more'] ? $page['next_cursor'] : null;
} while ($cursor !== null);
printf("%d pedidos lidos\n", count($orders));
{
"object": "list",
"data": [
{
"object": "order",
"id": "ord_8k2m4q6s1v3x5z7b9d0f2h",
"role": "OWNER",
"status": "PAID",
"currency": "BRL",
"amount_total": 27730,
"amount_subtotal": 29700,
"amount_in_brl": 27730,
"created_at": "2026-10-14T13:05:22.418Z",
"paid_at": "2026-10-14T13:05:24.902Z",
"canceled_at": null,
"updated_at": "2026-10-14T13:05:25.117Z",
"customer": {
"name": "Maria Oliveira",
"email": "maria.oliveira@example.com",
"phone": "+5511999998888",
"document": "12345678909",
"document_type": "CPF"
},
"shipping_address": {
"line1": "Rua das Flores",
"line2": "123",
"line3": "Apto 45",
"neighborhood": "Centro",
"city": "São Paulo",
"state": "SP",
"country": "BR",
"postal_code": "01001000",
"full_name": "Maria Oliveira"
},
"payment": {
"method": "CREDIT_CARD",
"status": "PAID",
"installments": 1,
"amount": 27730,
"interest_amount": 0,
"discount_amount": 1970,
"fee": 1386,
"amount_refunded": 0,
"refunded_at": null,
"decline_code": null,
"decline_message": null,
"card": {
"brand": "MASTERCARD",
"last4": "4242"
}
},
"items": [
{
"product_id": "prod_3n7p1r5t9w2y4a6c8e0g1j",
"name": "Curso Completo de Fotografia",
"quantity": 1,
"unit_price": 19700,
"sub_total": 19700,
"currency": "BRL",
"offer_id": "prod_offer_5d1f3h7k9m2p4r6t8v0x2z",
"plan_id": null,
"order_bump_id": null
},
{
"product_id": "prod_6b8d0f2h4k6m8p0r2t4v6x",
"name": "Pacote de Presets",
"quantity": 1,
"unit_price": 10000,
"sub_total": 10000,
"currency": "BRL",
"offer_id": null,
"plan_id": null,
"order_bump_id": "ob_2c4e6g8j0l2n4q6s8u0w2y"
}
],
"offer": {
"id": "prod_offer_5d1f3h7k9m2p4r6t8v0x2z",
"name": "Oferta de lançamento"
},
"promocode": {
"code": "BEMVINDO10",
"description": "10% de desconto no curso",
"amount": -1970
},
"utm": {
"utm_source": "instagram",
"utm_medium": "social",
"utm_campaign": "lancamento-outubro",
"utm_term": null,
"utm_content": "stories"
},
"tracking": {
"sck": "parceiro-123",
"src": "bio"
},
"navigator": {
"user_agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 18_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 Instagram 350.0.0",
"referer": "https://www.instagram.com/",
"device": "Mobile",
"browser": "Instagram",
"origin": "instagram.com"
},
"metadata": {}
},
{
"object": "order",
"id": "ord_1q3s5u7w9y0a2c4e6g8i0k",
"role": "COPRODUCER",
"status": "PAID",
"currency": "BRL",
"amount_total": 9700,
"amount_subtotal": 9700,
"amount_in_brl": 9700,
"created_at": "2026-10-13T18:42:10.036Z",
"paid_at": "2026-10-13T18:43:51.274Z",
"canceled_at": null,
"updated_at": "2026-10-13T18:43:52.009Z",
"customer": {
"name": "Maria Oliveira",
"email": "maria.oliveira@example.com",
"phone": "+5511999998888",
"document": "12345678909",
"document_type": "CPF"
},
"shipping_address": null,
"payment": {
"method": "PIX",
"status": "PAID",
"installments": 1,
"amount": 9700,
"interest_amount": 0,
"discount_amount": 0,
"fee": 485,
"amount_refunded": 0,
"refunded_at": null,
"decline_code": null,
"decline_message": null,
"card": null
},
"items": [
{
"product_id": "prod_9x1z3b5d7f9h1k3m5p7r9t",
"name": "Mentoria em Grupo",
"quantity": 1,
"unit_price": 9700,
"sub_total": 9700,
"currency": "BRL",
"offer_id": "prod_offer_4w6y8a0c2e4g6j8l0n2q4s",
"plan_id": null,
"order_bump_id": null
}
],
"offer": {
"id": "prod_offer_4w6y8a0c2e4g6j8l0n2q4s",
"name": "Mentoria em Grupo"
},
"promocode": null,
"utm": null,
"tracking": null,
"navigator": null,
"metadata": {
"order_parent_id": "ord_7h9k1m3p5r7t9v1x3z5b7d"
}
}
],
"has_more": true,
"next_cursor": "eyJ2IjoxLCJyIjoib3JkZXJzIiwicyI6ImNyZWF0ZWRfYXRfZGVzYyJ9.k3X9vQ2mT7bR1wL5yN8pZg"
}

