Skip to Content
API ReferenceRate Limits

Rate Limits

VDClip rate-limits the API so one integration cannot exhaust shared capacity. When you send requests faster than your plan allows, the API replies with 429 Too Many Requests and a Retry-After header telling you how long to wait.

This applies to the REST surface at https://api.vdclip.com/v1/* (authenticated with Authorization Bearer). The OAuth 2.1 endpoints under /oauth2/* and /.well-known/* are throttled separately.

i
Throughput depends on your plan

Request throughput is tied to your subscription, and the exact numbers can change as plans evolve. This page intentionally does not list fixed limits. Check the current per-plan throughput on the pricing page.

What a rate-limited response looks like

When you exceed your limit, the /v1 endpoints return HTTP 429 with the direct JSON response. The errors[].code is RATE_LIMITED, and a Retry-After header carries the seconds to wait.

HTTP/1.1 429 Too Many Requests content-type: application/json retry-after: 12 { "errors": [ { "code": "RATE_LIMITED", "description": "Rate limit exceeded. Retry after 12 seconds." } ] }
SignalMeaning
HTTP 429The request was rejected because you are over your limit.
errors[].codeRATE_LIMITED on the /v1 surface.
Retry-AfterSeconds to wait before sending the next request.
X-Request-IDInclude this when you contact support about a throttling issue.

Rate-limit headers

Every authenticated /v1 response includes these headers so you can track your usage without waiting for a 429:

HeaderMeaning
X-RateLimit-LimitMaximum requests allowed in the current window.
X-RateLimit-RemainingRequests remaining in the current window.
X-RateLimit-ResetUnix timestamp (seconds) when the window resets.

When Retry-After is present, wait at least that long before retrying. A tight retry loop will not unblock you sooner and may extend the cooldown.

Handling 429 in code

Honour Retry-After when present, otherwise fall back to exponential backoff with jitter. Retry only on 429 and 5xx responses, and give up after a bounded number of attempts.

const BASE_DELAY_MS = 1000 const MAX_DELAY_MS = 30_000 const MAX_ATTEMPTS = 5 async function callVdclip(path: string, init: RequestInit = {}) { let attempt = 0 let delay = BASE_DELAY_MS while (true) { const res = await fetch(`https://api.vdclip.com${path}`, { ...init, headers: { 'Authorization': process.env.VDCLIP_API_KEY!, // server-side only, never in the browser 'Content-Type': 'application/json', ...init.headers, }, }) // Success, or a non-retryable error: return and let the caller handle it. const isRetryable = res.status === 429 || res.status >= 500 if (!isRetryable || attempt >= MAX_ATTEMPTS - 1) return res // Prefer the server's Retry-After. Otherwise back off exponentially with jitter. const retryAfter = Number(res.headers.get('retry-after')) * 1000 const jitter = Math.random() * delay * 0.4 const wait = retryAfter > 0 ? retryAfter : delay + jitter await new Promise((resolve) => setTimeout(resolve, wait)) delay = Math.min(delay * 2, MAX_DELAY_MS) attempt++ } }

The same logic works in any language. Two rules matter: trust Retry-After first, and add randomness to your fallback delay so that many clients do not all retry at the same instant.

Which errors to retry

Not every failure should be retried. Repeating a request that failed for a client-side reason wastes attempts and can trip abuse protection.

ErrorRetryableWhat to do
RATE_LIMITED (429)YesWait for Retry-After, then retry with backoff.
UPSTREAM_ERROR / 5xxYesRetry with exponential backoff and jitter.
INVALID_API_KEYNoThe key is wrong or revoked. Fix the key, then resend.
MISSING_SCOPENoThe key lacks the required scope. See Authentication.
INSUFFICIENT_CREDITSNoTop up before retrying. See pricing .
VALIDATION_ERRORNoThe request body is invalid. Fix it before resending.
INVALID_JSONNoThe body is not valid JSON. Fix it before resending.
NOT_FOUNDNoThe resource does not exist. Do not retry.

For the full list of error codes and their shapes, see Errors.

OAuth 2.1 endpoints

The OAuth 2.1 Authorization Server at /oauth2/* and /.well-known/* is rate limited independently of the API-key surface and uses standard RFC bodies. If you build an MCP, agent, or integration client that authenticates with Authorization: Bearer <JWT>, apply the same discipline: respect Retry-After, back off on 429, and cache discovery documents and JWKS instead of fetching them on every call.

Reducing how often you hit the limit

A few habits keep you comfortably under your throughput.

  • Paginate instead of polling hard. List endpoints use cursor pagination (cursor and limit). Page through results with the returned cursor rather than re-requesting the first page in a loop.
  • Poll long-running work gently. Renders and other async jobs finish on their own schedule. Poll status on an interval with backoff instead of in a tight loop.
  • Batch where you can. Group related work into fewer requests when an endpoint supports it.
  • Cache stable data. Templates, brand-kit assets, and account details change rarely. Cache them on your side instead of re-fetching for every operation.

Tips & Best Practices

Always honour Retry-After

When a 429 includes a Retry-After header, wait at least that many seconds. It is the server telling you exactly when capacity is available again.

Add jitter to your backoff

Randomize your fallback delay so that many clients recovering at once do not retry in lockstep and re-trigger the limit.

Never retry client errors

INVALID_API_KEY, MISSING_SCOPE, VALIDATION_ERROR, and similar codes will fail every time until you fix the request. Retrying only burns attempts.

Keep the request_id

Log X-Request-ID from throttled responses so support can trace the exact calls if you think a limit is wrong.

Frequently Asked Questions

Last updated on