On this page

    Get the right capture

    Capture Pages Behind a Login

    Screenshot dashboards, admin pages and staging sites: send a session cookie, basic auth or a bearer token, and make a login page fail instead of render.

    Time
    20 minutes
    Plan
    Free
    You need
    • An API key in RS_API_KEY, on your server
    • A user account for screenshots with read-only access

    What you will have at the end

    The test page httpbin.org/cookies shows the cookies it received. The sid cookie came from the request.

    The renderer starts every capture as a new visitor with no cookies. To capture a page that needs a login, you send the credential with the request. You will pick the right method, send it, and make sure that a wrong credential gives an error instead of a picture of the login form.

    Pick the method that matches the site

    The page is protected by Send Typical sites
    A login form and a session cookie network.cookies SaaS dashboards, admin panels
    HTTP basic auth (a browser password box) network.auth, type: "basic" Staging and preview sites
    A bearer token network.auth, type: "bearer" Internal report pages
    A custom header network.headers Tenant IDs, preview tokens

    These options work with POST only. Never put credentials in a URL: URLs end up in logs.

    Without a credential, the site sends the renderer to its login page. That is what you get as an image:

    github.com/settings/profile without a session: the capture shows the sign-in page. Step 4 turns this into an error.

    Create a user just for screenshots, with read-only access. Then, in your capture job, sign in over HTTP, read the session cookie, and pass it to the API:

    // capture-dashboard.mjs: Node 20+, no dependencies
    import { writeFile } from 'node:fs/promises';
    
    const APP = 'https://app.example.com';
    
    // 1. Sign in like a browser, but keep the redirect so you can read Set-Cookie.
    const login = await fetch(`${APP}/login`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: new URLSearchParams({ email: process.env.SCREENSHOT_USER, password: process.env.SCREENSHOT_PASSWORD }),
      redirect: 'manual',
    });
    const setCookie = login.headers.getSetCookie().find((cookie) => cookie.startsWith('sid='));
    if (!setCookie) throw new Error(`login failed with HTTP ${login.status}`);
    const sid = setCookie.split(';')[0].slice('sid='.length);
    
    // 2. Capture with the cookie.
    const response = await fetch('https://api.renderscreenshot.com/v1/screenshot', {
      method: 'POST',
      headers: { Authorization: `Bearer ${process.env.RS_API_KEY}`, 'Content-Type': 'application/json' },
      body: JSON.stringify({
        url: `${APP}/dashboard`,
        network: {
          cookies: [{ name: 'sid', value: sid, domain: 'app.example.com', path: '/', secure: true, http_only: true }],
        },
        wait: { for_selector: '.account-menu', timeout: 20 },
        cache: { ttl: 3600 },
      }),
    });
    if (!response.ok) throw new Error(`capture failed: ${response.status} ${await response.text()}`);
    await writeFile('dashboard.png', Buffer.from(await response.arrayBuffer()));
    

    The image at the top of this guide shows that the cookie arrives: httpbin.org/cookies lists every cookie it receives.

    Most cookie problems come from three fields:

    • domain must match the page's host, or a parent domain the app uses (example.com for app.example.com).
    • secure: true is needed when the app marks its cookie Secure, which HTTPS apps usually do.
    • Some apps need two cookies (a session and a CSRF cookie). Copy every cookie the browser sends, from your browser's developer tools.

    Use basic auth or a token

    For a staging site with a browser password box:

    curl -X POST https://api.renderscreenshot.com/v1/screenshot \
      -H "Authorization: Bearer $RS_API_KEY" -H "Content-Type: application/json" \
      -d '{
        "url": "https://staging.example.com/",
        "network": { "auth": { "type": "basic", "username": "staging", "password": "'"$STAGING_PASSWORD"'" } }
      }' --output staging.png
    
    httpbin.org/basic-auth/staging/preview returns "authenticated": true when the username and password arrive.

    For a page that takes a bearer token, and a custom header for the tenant:

    {
      "url": "https://app.example.com/report",
      "network": {
        "auth": { "type": "bearer", "token": "<token>" },
        "headers": { "X-Tenant": "acme" }
      }
    }
    

    The API sends these headers only to the page's own origin. Scripts and images from other hosts do not get them.

    Make the login page fail

    When a session expires, the app shows its login page, and you get a sharp image of a login form. Prevent that: wait for an element that only signed-in users see, such as the account menu.

    {
      "url": "https://app.example.com/dashboard",
      "network": { "cookies": [{ "name": "sid", "value": "<session>", "domain": "app.example.com" }] },
      "wait": { "for_selector": ".account-menu", "timeout": 20 }
    }
    

    If the login page loads instead, the wait times out and the request fails with timeout. Failed requests cost no credits. When you see that error, sign in again and retry once.

    Keep the screenshots private

    Screenshots of private pages need extra care:

    • The image has a public CDN link (X-Cache-URL) until the cache entry expires. Set "cache": { "ttl": 3600 }, the minimum, and purge the entry when you have the file: send the X-Cache-Key header value to POST /v1/cache/purge (cache invalidation).
    • Logs hide credentials. Cookie values and network.auth values show as [FILTERED] in your request logs. Custom network.headers are stored as sent, so put secrets in network.auth or network.cookies.
    • Short-lived sessions. Sign in once per job and sign out after.
    • Server only. These requests carry your API key and the app credential. Never send them from a browser.

    Next steps

    Try RenderScreenshot free

    150 free credits every month, no credit card required.