API & webhooks

API rate limits and best practices

Overview

To keep the API reliable for everyone, Kyvento limits the request rate per account. This article explains the limits, the associated HTTP headers and the patterns that let your integration work efficiently and retry-safely.

The rate limit

For API token access, a quota applies per account and minute, staggered by your plan:

  • Starter: 30 requests per minute
  • Growth: 100 requests per minute
  • Pro: 280 requests per minute
  • Scale: 600 requests per minute

During the trial, the Starter quota applies (30 requests per minute). Every response carries three headers with which your application knows its budget:

  • X-RateLimit-Limit – your plan's per-minute quota
  • X-RateLimit-Remaining – remaining requests in the current one-minute window
  • X-RateLimit-Reset – the time (Unix timestamp) at which the full quota is available again

These headers are the authoritative source for your current quota: read X-RateLimit-Limit at runtime instead of hard-coding the number into your integration – that keeps your application correct if your plan changes or the limits are adjusted.

Once the quota is exhausted, Kyvento responds with HTTP 429, the error code RATE_LIMITED and a Retry-After header. Never treat 429 as an error in your logic; instead, wait the indicated time and repeat the request – ideally with additional random jitter if several processes work in parallel.

Additional volume limits for heavy operations

The per-minute quota limits how often you call – not how much a single call produces. Three particularly expensive operations therefore carry a quota of their own that applies in addition to the plan limit:

  • POST /exports/bulk-pdf5 per hour. The most expensive operation: a ZIP over up to 500 PDF documents.
  • POST /exports and POST /exports/datev20 per hour, a shared quota for export generation.
  • GET /search120 per minute, per user.

Which of the two limits bites first depends on your plan: for search, the per-minute quota of Starter (30) and Growth (100) sits below the 120 – there the plan limit still does the braking. And unlike that one, which counts for the entire account, search is measured per user: what counts is the signed-in user, or the owner of the token being used. Several tokens belonging to the same user share the quota – so one access cannot bring your colleagues' search to a halt.

The quotas are counted separately from each other and from the plan limit, so an exhausted bulk-PDF quota blocks neither search nor the rest of your API traffic. Retrieving exports that already exist – list, detail view and download – is not throttled: a client polling for completion therefore does not run into the generation limit. When a quota is exceeded, Kyvento responds as usual with 429 and a Retry-After header.

Safe retries: the idempotency key

If a write request breaks off (timeout, network error), you don't know whether it went through on the server side. Therefore, for critical POST requests, send the Idempotency-Key header with a self-generated, unique value (recommended: UUID). Kyvento remembers the result for 24 hours: a repeat with the same key returns the original response instead of executing the operation again. The same key with changed request content is rejected – this protects against programming errors.

Monetary amounts in the API

Every monetary amount leaves the API as a decimal string in euros – for example "19.90", not 1990 and not 19.9 as a number. The reason is precision: product and unit prices allow more than two decimal places, which neither integer cents nor floating-point numbers represent without loss. So read the values into a decimal type (such as decimal.Decimal or BigDecimal) rather than a float.

This applies consistently to the v1 API, the customer portal and the checkout. One exception when writing: a coupon's minimum order value (minimum_order_amount) is still expected as an integer in cents when creating and updating, while the response returns it as a decimal string. Do not, therefore, pass a value you have read straight back into a write payload.

Using pagination correctly

  • Lists return data plus meta with per_page, has_more and follow-up page information.
  • per_page accepts up to 1000 entries – for bulk reconciliations, choose large pages; this saves requests against your quota.
  • For large, changing data sets, cursor pagination (?pagination=cursor) is recommended: you page onward stably via meta.next_cursor, even when records are created or disappear in parallel.

Best practices for economical integrations

  1. Webhooks instead of polling: Have changes reported to you via webhook instead of querying lists every minute – it's faster and consumes practically no quota (see Configuring webhooks).
  2. Watch your budget: Log X-RateLimit-Remaining and throttle batch jobs before the limit kicks in.
  3. Back off exponentially: On 429 and 5xx, retry at growing intervals – never in a tight loop.
  4. Evaluate errors machine-readably: Branch on the error_code of the response, not on message texts.
  5. Spread out bulk reconciliations: Distribute large synchronizations over time instead of exhausting the per-minute quota in short bursts – this keeps budget free for concurrent daytime operations.

Next steps

← Back to Support

Related articles

API & webhooks

Importing data via the REST API

Overview When migrating to Kyvento from another system, you transfer your existing records – customers, subscriptions, s...

API & webhooks

Verifying a webhook endpoint

Overview Kyvento delivers webhook events exclusively to verified endpoints. Verification establishes that the configured...

API & webhooks

Setting up the Stripe webhook

Overview A webhook is Stripe's way of reporting back to Kyvento: whenever something happens in Stripe that Kyvento needs...