Guides
Pagination and rate limits
Lists return pages, newest first, and every key may make 100 requests per minute with clear headers.
Lists
List endpoints (/v1/invoices, /v1/events, /v1/webhook_endpoints) return objects newest first in an envelope:
{ "object": "list", "url": "/v1/invoices", "has_more": true, "data": [ ... ] }
| Parameter | Meaning |
|---|---|
limit |
Objects per page: 1-100, default 10. |
starting_after |
Id of the last object of the previous page. |
When has_more is true, ask for the next page with starting_after set to the id of the last item in data.
AFTER=""
while :; do
PAGE=$(curl -s "https://app.ksefshop.pl/v1/invoices?limit=100${AFTER:+&starting_after=$AFTER}" \
-H "Authorization: Bearer $KSEF_API_KEY")
echo "$PAGE" | jq -r '.data[] | [.number, .ksef_number, .status] | @tsv'
[ "$(echo "$PAGE" | jq -r .has_more)" = "true" ] || break
AFTER=$(echo "$PAGE" | jq -r '.data[-1].id')
done<?php
function all_invoices(): Generator
{
$after = null;
do {
$query = http_build_query(array_filter(['limit' => 100, 'starting_after' => $after]));
$ch = curl_init('https://app.ksefshop.pl/v1/invoices?' . $query);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('KSEF_API_KEY')],
]);
$page = json_decode(curl_exec($ch), true);
curl_close($ch);
yield from $page['data'];
$after = $page['data'] ? end($page['data'])['id'] : null;
} while ($page['has_more']);
}
foreach (all_invoices() as $invoice) {
echo $invoice['number'], ' ', $invoice['ksef_number'], PHP_EOL;
}async function* allInvoices() {
let after = null;
while (true) {
const url = new URL("https://app.ksefshop.pl/v1/invoices");
url.searchParams.set("limit", "100");
if (after) url.searchParams.set("starting_after", after);
const res = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.KSEF_API_KEY}` },
});
const page = await res.json();
if (!res.ok) throw new Error(`${page.error.code}: ${page.error.message}`);
yield* page.data;
if (!page.has_more) return;
after = page.data.at(-1).id;
}
}
for await (const invoice of allInvoices()) {
console.log(invoice.number, invoice.ksef_number);
}import os
import requests
def all_invoices():
after = None
while True:
params = {"limit": 100}
if after:
params["starting_after"] = after
page = requests.get(
"https://app.ksefshop.pl/v1/invoices",
headers={"Authorization": f"Bearer {os.environ['KSEF_API_KEY']}"},
params=params,
timeout=30,
).json()
yield from page["data"]
if not page["has_more"]:
return
after = page["data"][-1]["id"]
for invoice in all_invoices():
print(invoice["number"], invoice["ksef_number"])Filters
GET /v1/invoices?external_id=SO-1001returns the invoice of one order (with corrections).GET /v1/events?type=invoice.rejectedreturns events of one type only.
Rate limits
Each key may make 100 requests per minute. Every response carries:
| Header | Meaning |
|---|---|
RateLimit-Limit |
The limit per window (100). |
RateLimit-Remaining |
Requests left. |
RateLimit-Reset |
Seconds until the window resets. |
Over the limit you get 429 rate_limited with a Retry-After header in seconds. Retry code is in the idempotency guide.
Staying under the limit
- Receive webhooks instead of polling every invoice every few seconds.
- If you poll, poll every 3-5 seconds and stop at a final state.
- For reports, fetch pages of 100 objects rather than single invoices.
- Test and live keys have separate limits.