Skip to main content

Rate limits

An undocumented limit is indistinguishable from an outage, so here are the numbers.

SurfaceLimitCounted per
POST /oauth2/token20 per minutecredential, and separately per calling address
Everything under /v1600 per minutecredential

The /v1 limit is per credential, not per IP address. Two partners sharing a cloud egress address do not throttle each other, and running your integration from a dozen containers behind one NAT gateway does not count as one caller.

There is also a coarser per-address ceiling underneath, sized well above the per-credential limit so that a single partner spending its full 600 never reaches it. If you are seeing 429 while RateLimit-Remaining looks healthy, you are sharing an address with a lot of traffic — tell us and we will look.

Reading your budget

Every /v1 response carries three headers:

RateLimit-Limit: 600
RateLimit-Remaining: 587
RateLimit-Reset: 42
  • RateLimit-Limit — requests per minute for your credential.
  • RateLimit-Remaining — how many you have left right now.
  • RateLimit-Reset — seconds until your budget is back to full.

The bucket refills continuously rather than resetting on a fixed boundary, so you regain roughly ten requests a second as you go. You do not have to wait for Reset to hit zero before the next request succeeds — that value is the wait for the whole budget, not for the next single request.

note

Limits are enforced per API instance, and we run more than one. In practice this means your effective ceiling is somewhat higher than the published number, and RateLimit-Remaining can move non-monotonically as your requests land on different instances. Treat it as a live signal to back off on, not as an exact ledger.

When you exceed it

429 Too Many Requests, with a Retry-After in seconds and the same problem-details body as every other error:

HTTP/1.1 429 Too Many Requests
Retry-After: 3
RateLimit-Limit: 600
RateLimit-Remaining: 0
RateLimit-Reset: 58
Content-Type: application/problem+json
{
"type": "https://api.valueverde.de/errors/rate-limited",
"title": "Too Many Requests",
"status": 429,
"detail": "Rate limit exceeded for this API client. Retry after the period given in Retry-After, and see https://docs.valueverde.de/docs/rate-limits.",
"instance": "/v1/cooperatives",
"code": "RATE_LIMITED",
"trace_id": "9f2c1b7e-4d3a-4f18-93ac-2b6e5d0c8a71"
}

Sleep for Retry-After and try again. A refused request costs you nothing from the bucket, so retrying does not push your own recovery further away — but retrying without sleeping will not succeed any sooner either.

Quote trace_id if you contact support about a specific call.

Staying under it

Cache your access token. This is the single most common cause of a 429 on the token endpoint. A token is valid for 15 minutes; mint one, hold it, and re-mint when it expires. An integration that fetches a token before every call will exhaust twenty a minute almost immediately — and it is doing an RSA signature per call for no reason.

Use conditional requests on the catalogue. GET /v1/cooperatives and GET /v1/cooperatives/{id} return ETag, and a 304 Not Modified still costs a request but nothing else. See Pagination & caching.

Back off on the header, not on the failure. If you are checking RateLimit-Remaining and slowing down as it approaches zero, you will rarely see a 429 at all.

Ask before you engineer around it. If a bulk reconciliation genuinely needs more than 600 a minute, that is a reasonable thing to want — email partners@valueverde.de rather than sharding across credentials, which we would read as abuse.

Other surfaces

The public unauthenticated catalogue (/public/**) and the browser authentication endpoints have their own, tighter, per-address limits. Neither is part of the partner contract; if you are calling them from an integration, you are on the wrong endpoint.