Rate Limits
Corvo rate limits are application-level sliding windows keyed to the authenticated organization. When a request is throttled, the API returns HTTP 429 and a `Retry-After` header in seconds.
| Endpoint | Method | Limit |
|---|---|---|
| /api/v1/status | GET | No application-level limit |
| /api/v1/documents/upload | POST | 30/min per organization |
| /api/v1/documents/upload-url | POST | 30/min per organization |
| /api/v1/documents/confirm | POST | 30/min per organization |
| /api/v1/shipments | POST | 30/min per organization |
| /api/v1/shipments | GET | 60/min per organization |
| /api/v1/shipments/{id} | GET | 1 request / 5 min / shipment for API keys |
| /api/v1/shipments/{id}/buy | POST | 30/min per organization |
| /api/v1/shipments/{id}/cancel | POST | 30/min per organization |
| /api/v1/shipments/{id}/proof | GET | No application-level limit |
| /api/v1/shipments/{id}/artifacts | GET | No application-level limit |
| /api/v1/shipments/{id}/artifacts/{artifactId} | GET | No application-level limit |
| /api/v1/shipments/{id}/evidence-bundle | GET | No application-level limit |
Polling cadence matters
Shipment detail polling is intentionally conservative for API keys: one request per shipment every five minutes, with cached responses inside that window.
Response429Rate limited
{
"error": {
"message": "Too many requests. Please try again later.",
"code": "RATE_LIMITED"
}
}async function fetchWithRetry(url: string, options: RequestInit) {
const response = await fetch(url, options);
if (response.status !== 429) {
return response;
}
const retryAfter = response.headers.get("Retry-After");
const waitSeconds = retryAfter ? Number.parseInt(retryAfter, 10) : 1;
await new Promise((resolve) => setTimeout(resolve, waitSeconds * 1000));
return fetch(url, options);
}