On this page

    Run in production

    Prepare Your Integration for Production

    A checklist before launch: one key per environment, timeouts, retries that respect Retry-After, rate limits, a credit alert and request IDs in your logs.

    Time
    30 minutes
    Plan
    Free
    You need
    • A working integration (see the first screenshot guide)
    • Access to your app's secret store and logs

    What you will have at the end

    One key per environment and use. If a key leaks, you revoke it without breaking the others.

    The API works the same in production as on your laptop. What changes is the traffic: more requests, slow pages, and nobody watching. Work through these six steps before launch.

    Use one key per environment

    Create one API key for each place that calls the API: Production server, Staging, Nightly job. Store each key in that environment's secret store, never in the repository.

    Last used tells you which keys are still in use. Revoke stops a key at once.

    To rotate a key without downtime:

    1. Create the new key.
    2. Deploy it to the environment.
    3. Check in API Keys that Last used of the old key stops changing.
    4. Click Revoke on the old key.

    All keys share the account's credits and rate limit. Keys for the browser are different: use signed URLs there, never an rs_live_ key.

    Set your client timeout above the page timeout

    The API waits up to wait.timeout for a page (30 seconds by default, 60 at most), and a render takes a few more seconds. If your HTTP client gives up first, you get an error while the render finishes on the server and uses a credit. Set the client timeout to about 90 seconds:

    Node.js

    const client = new Client(process.env.RS_API_KEY, { timeout: 90_000 }); // milliseconds
    

    Python

    client = Client(os.environ['RS_API_KEY'], timeout=90.0)  # seconds
    

    curl

    curl --max-time 90 ...
    

    Retry only the errors that can succeed

    HTTP Examples Retry?
    400, 401, 403, 404 invalid_url, missing_credentials, insufficient_credits Never. Fix the request or alert a person.
    422 timeout, navigation_failed Once, after a short wait
    422 blocked, ssl_error, content_check_failed No
    429 too_many_requests, concurrent_limit Yes, after Retry-After seconds
    500, no response internal_error, network error Yes, with exponential backoff

    Failed requests use no credits, so retries cost time, not money. A complete retry helper:

    Node.js

    import { Client, TakeOptions, RenderScreenshotError } from 'renderscreenshot';
    
    const client = new Client(process.env.RS_API_KEY, { timeout: 90_000 });
    const shouldRetry = (status) => !status || status === 408 || status === 422 || status === 429 || status >= 500;
    
    export async function takeWithRetry(options, maxAttempts = 3) {
      for (let attempt = 1; ; attempt++) {
        try {
          return await client.take(options);
        } catch (error) {
          if (!(error instanceof RenderScreenshotError)) throw error;
          if (!shouldRetry(error.httpStatus) || attempt >= maxAttempts) throw error;
          const wait = error.retryAfter ? error.retryAfter * 1000 : Math.random() * 1000 * 2 ** (attempt - 1);
          await new Promise((resolve) => setTimeout(resolve, wait));
        }
      }
    }
    

    Python

    import os
    import random
    import time
    
    from renderscreenshot import Client, RenderScreenshotError
    
    client = Client(os.environ['RS_API_KEY'], timeout=90.0)
    
    
    def should_retry(status):
        return not status or status in (408, 422, 429) or status >= 500
    
    
    def take_with_retry(options, max_attempts=3):
        for attempt in range(1, max_attempts + 1):
            try:
                return client.take(options)
            except RenderScreenshotError as e:
                if not should_retry(e.http_status) or attempt == max_attempts:
                    raise
                time.sleep(e.retry_after or random.uniform(0, 2 ** (attempt - 1)))
    

    For blocked and content_check_failed, check error.code before you retry: their status is 422, but a retry gets the same result.

    Stay under the rate limit

    The rate limit counts requests per minute for the whole account:

    Plan Requests per minute
    Free 10
    Starter 60
    Growth 120
    Scale 200

    Every response has X-RateLimit-Remaining. A 429 has Retry-After, the seconds until the window resets. Keep the number of parallel workers small: a render takes 2 to 5 seconds, so 4 workers already send about 60 requests a minute. More workers only produce more 429 errors.

    Alert before the credits run out

    At 0 credits, every request fails with 403 insufficient_credits until the reset or an upgrade. Check the balance once an hour. The call is free:

    curl -sS https://api.renderscreenshot.com/v1/usage -H "Authorization: Bearer $RS_API_KEY"
    
    {
      "plan": { "id": "starter", "name": "Starter", "credits": 2500 },
      "credits": 2500,
      "used": 2112,
      "remaining": 388,
      "period_end": "2026-11-05T08:00:00Z",
      "rate_limit": { "limit": 60, "remaining": 59, "reset": 1792644660, "window": 60 }
    }
    

    Alert when remaining is below what you use in two or three days. Watch credits and change plans shows the same numbers in the dashboard.

    Log the request ID

    Every response has a request ID: the X-Request-Id header, the id field in JSON responses, and request_id in errors. Log it with your own job ID. In Logs, each request shows its ID, options and result:

    When you contact support, send the request ID. It finds the exact request in seconds.

    Next steps

    Try RenderScreenshot free

    150 free credits every month, no credit card required.