Postman Collection for the Screenshot API

Import a tested Postman collection for the RenderScreenshot API: GET, POST, batch, usage and signed URLs built in a pre-request script, with tests on each.

By Simon Chiu · Updated

Most people meet an HTTP API in Postman before they write a line of code. This collection gives you eight requests that cover the whole RenderScreenshot API surface you will use first: a GET that returns image bytes, a GET that returns JSON, a POST with nested options, a batch and its status, your usage, a signed URL built in a pre-request script, and a deliberate error so that you see the error envelope. Each request has tests, so the same files also run headless with newman. All integrations are listed on the integrations hub; if you prefer a terminal, the raw HTTP guide covers the same calls with curl.

Last tested: 2026-10-18 with newman 6.2.3 (the Postman collection runner, which uses the same script sandbox as the Postman app) on Node 22.18. All 8 requests and 9 tests passed against a local stub that implements our documented responses and checks signatures the way the API does. The signed-URL request also passed with a target URL that contains a space, & and ü, and it failed as expected with a wrong secret key. We did not publish the collection to the Postman API Network or take Postman app screenshots for this page.

What you can do with it

  • Try options before you write code. Change a query value or a JSON field, send, and look at the image in the Body preview.
  • Produce a signed URL for an <img> tag. Request 7 builds a URL that needs no API key and expires in an hour. It also saves it in the lastSignedUrl variable so that you can copy it.
  • Smoke-test your account in CI. newman run exercises authentication, rendering, batch and usage, and fails the job if any test fails.

Prerequisites

  • The Postman app (desktop or web) or newman.
  • From the RenderScreenshot dashboard: a live key (rs_live_) for requests 1 to 6 and 8, and a public key ID (rs_pub_) plus secret key (rs_secret_) for request 7. Create a free account to get them.

Import the collection and environment

  1. Save the two listings below as renderscreenshot.postman_collection.json and renderscreenshot.postman_environment.json.
  2. In Postman, click Import and drop both files.
  3. Open Environments → RenderScreenshot (production) and fill apiKey, keyId and secretKey. They are marked as secret, so Postman masks them on screen.
  4. Select the environment in the top-right menu and send request 1.

Collection

{
  "info": {
    "name": "RenderScreenshot API",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {"type": "bearer", "bearer": [{"key": "token", "value": "{{apiKey}}", "type": "string"}]},
  "item": [
    {
      "name": "1. GET screenshot (image bytes)",
      "request": {
        "method": "GET",
        "header": [],
        "url": {
          "raw": "{{baseUrl}}/v1/screenshot?url={{targetUrl}}&preset=og_card",
          "host": ["{{baseUrl}}"],
          "path": ["v1", "screenshot"],
          "query": [{"key": "url", "value": "{{targetUrl}}"}, {"key": "preset", "value": "og_card"}]
        }
      },
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "pm.test('200 with an image', () => {",
              "  pm.response.to.have.status(200);",
              "  pm.expect(pm.response.headers.get('Content-Type')).to.match(/^image\\//);",
              "});",
              "pm.test('metadata headers present', () => {",
              "  pm.expect(pm.response.headers.get('X-Cache-URL')).to.match(/^https:/);",
              "  console.log('credits used', pm.response.headers.get('X-Credits-Used'), 'title', pm.response.headers.get('X-Screenshot-Title'));",
              "});"
            ]
          }
        }
      ]
    },
    {
      "name": "2. GET screenshot (JSON + CDN link)",
      "request": {
        "method": "GET",
        "header": [{"key": "Accept", "value": "application/json"}],
        "url": {
          "raw": "{{baseUrl}}/v1/screenshot?url={{targetUrl}}&preset=desktop_hd&format=webp&block_cookies=true&cache_available=true",
          "host": ["{{baseUrl}}"],
          "path": ["v1", "screenshot"],
          "query": [
            {"key": "url", "value": "{{targetUrl}}"},
            {"key": "preset", "value": "desktop_hd"},
            {"key": "format", "value": "webp"},
            {"key": "block_cookies", "value": "true"},
            {"key": "cache_available", "value": "true"}
          ]
        }
      },
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "const res = pm.response.json();",
              "pm.test('completed with a CDN link', () => {",
              "  pm.response.to.have.status(200);",
              "  pm.expect(res.status).to.eql('completed');",
              "  pm.expect(res.image.url).to.match(/^https:/);",
              "});",
              "pm.collectionVariables.set('lastCacheKey', res.cache.key);"
            ]
          }
        }
      ]
    },
    {
      "name": "3. POST screenshot (nested JSON)",
      "request": {
        "method": "POST",
        "header": [{"key": "Accept", "value": "application/json"}, {"key": "Content-Type", "value": "application/json"}],
        "url": {"raw": "{{baseUrl}}/v1/screenshot", "host": ["{{baseUrl}}"], "path": ["v1", "screenshot"]},
        "body": {
          "mode": "raw",
          "raw": "{\n  \"url\": \"{{targetUrl}}\",\n  \"viewport\": {\n    \"width\": 1280,\n    \"height\": 800\n  },\n  \"capture\": {\n    \"mode\": \"full_page\"\n  },\n  \"output\": {\n    \"format\": \"jpeg\",\n    \"quality\": 80\n  },\n  \"wait\": {\n    \"until\": \"networkidle\",\n    \"timeout\": 30\n  },\n  \"block\": {\n    \"ads\": true,\n    \"cookie_banners\": true\n  }\n}",
          "options": {"raw": {"language": "json"}}
        }
      },
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "pm.test('200 and image size reported', () => {",
              "  pm.response.to.have.status(200);",
              "  pm.expect(pm.response.json().image.width).to.be.a('number');",
              "});"
            ]
          }
        }
      ]
    },
    {
      "name": "4. POST batch",
      "request": {
        "method": "POST",
        "header": [{"key": "Content-Type", "value": "application/json"}],
        "url": {"raw": "{{baseUrl}}/v1/batch", "host": ["{{baseUrl}}"], "path": ["v1", "batch"]},
        "body": {
          "mode": "raw",
          "raw": "{\n  \"urls\": [\n    \"{{targetUrl}}\",\n    \"https://example.org\"\n  ],\n  \"options\": {\n    \"preset\": \"og_card\"\n  }\n}",
          "options": {"raw": {"language": "json"}}
        }
      },
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "const res = pm.response.json();",
              "pm.test('batch accepted', () => pm.expect(['completed', 'processing']).to.include(res.status));",
              "pm.collectionVariables.set('batchId', res.id);",
              "(res.results || []).filter((r) => r.status === 'failed')",
              "  .forEach((r) => console.warn('failed', r.url, r.error));"
            ]
          }
        }
      ]
    },
    {
      "name": "5. GET batch status",
      "request": {
        "method": "GET",
        "header": [],
        "url": {
          "raw": "{{baseUrl}}/v1/batch/{{batchId}}",
          "host": ["{{baseUrl}}"],
          "path": ["v1", "batch", "{{batchId}}"]
        }
      },
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": ["pm.test('status known', () => pm.expect(pm.response.json().status).to.be.a('string'));"]
          }
        }
      ]
    },
    {
      "name": "6. GET usage",
      "request": {
        "method": "GET",
        "header": [],
        "url": {"raw": "{{baseUrl}}/v1/usage", "host": ["{{baseUrl}}"], "path": ["v1", "usage"]}
      },
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "const u = pm.response.json();",
              "pm.test('usage returned', () => pm.expect(u.remaining).to.be.a('number'));",
              "console.log(`credits left: ${u.remaining}, rate limit: ${u.rate_limit.limit}/min`);"
            ]
          }
        }
      ]
    },
    {
      "name": "7. Signed URL (no API key)",
      "request": {
        "method": "GET",
        "auth": {"type": "noauth"},
        "header": [],
        "url": {
          "raw": "{{baseUrl}}/v1/screenshot?url={{targetUrl}}&preset=og_card",
          "host": ["{{baseUrl}}"],
          "path": ["v1", "screenshot"],
          "query": [{"key": "url", "value": "{{targetUrl}}"}, {"key": "preset", "value": "og_card"}]
        }
      },
      "event": [
        {
          "listen": "prerequest",
          "script": {
            "type": "text/javascript",
            "exec": [
              "// Builds a signed URL from this request's query params. Type values unencoded.",
              "const esc = (v) => encodeURIComponent(v)",
              "  .replace(/[!'()*]/g, (c) => '%' + c.charCodeAt(0).toString(16).toUpperCase())",
              "  .replace(/%20/g, '+'); // CGI.escape rules, as the API expects",
              "const params = {};",
              "pm.request.url.query.each((p) => {",
              "  if (!p.disabled && !['signature', 'expires', 'key_id'].includes(p.key)) {",
              "    params[p.key] = pm.variables.replaceIn(p.value);",
              "  }",
              "});",
              "params.key_id = pm.variables.get('keyId');",
              "params.expires = String(Math.floor(Date.now() / 1000) + 3600); // 1 hour; the maximum is 30 days",
              "const query = Object.entries(params).map(([k, v]) => `${esc(k)}=${esc(v)}`).sort().join('&');",
              "const cryptoJs = require('crypto-js');",
              "const signature = cryptoJs.HmacSHA256(query, pm.variables.get('secretKey')).toString(cryptoJs.enc.Hex);",
              "pm.request.url = `${pm.variables.get('baseUrl')}/v1/screenshot?${query}&signature=${signature}`;",
              "pm.collectionVariables.set('lastSignedUrl', pm.request.url.toString());"
            ]
          }
        },
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": ["pm.test('signature accepted', () => pm.response.to.have.status(200));"]
          }
        }
      ]
    },
    {
      "name": "8. Error envelope (bad URL)",
      "request": {
        "method": "GET",
        "header": [{"key": "Accept", "value": "application/json"}],
        "url": {
          "raw": "{{baseUrl}}/v1/screenshot?url=not-a-url",
          "host": ["{{baseUrl}}"],
          "path": ["v1", "screenshot"],
          "query": [{"key": "url", "value": "not-a-url"}]
        }
      },
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "const { error } = pm.response.json();",
              "pm.test('400 invalid_url, not retryable', () => {",
              "  pm.response.to.have.status(400);",
              "  pm.expect(error.code).to.eql('invalid_url');",
              "  pm.expect(error.retryable).to.eql(false);",
              "});"
            ]
          }
        }
      ]
    }
  ],
  "variable": [
    {"key": "batchId", "value": ""},
    {"key": "lastCacheKey", "value": ""},
    {"key": "lastSignedUrl", "value": ""}
  ]
}

Environment

{
  "name": "RenderScreenshot (production)",
  "values": [
    {"key": "baseUrl", "value": "https://api.renderscreenshot.com", "type": "default", "enabled": true},
    {"key": "targetUrl", "value": "https://example.com", "type": "default", "enabled": true},
    {"key": "apiKey", "value": "", "type": "secret", "enabled": true},
    {"key": "keyId", "value": "", "type": "default", "enabled": true},
    {"key": "secretKey", "value": "", "type": "secret", "enabled": true}
  ]
}

What each request teaches

# Request What to look at
1 GET, image bytes The X-Cache-URL, X-Credits-Used and X-Screenshot-Title response headers; the Body preview shows the PNG
2 GET with Accept: application/json image.url (a CDN link you can share), cache.expires_at, usage.remaining; the test saves cache.key
3 POST, nested JSON The same request shape the SDKs send: viewport, capture, output, wait, block
4 POST /v1/batch Up to 10 URLs answer at once; larger batches return processing and an id
5 GET /v1/batch/:id Uses the batchId saved by request 4
6 GET /v1/usage Credits left this period and your account's per-minute limit; costs no credit
7 Signed URL No Authorization header at all; the URL carries key_id, expires and signature
8 Bad URL The error envelope: type, code, retryable, docs

The collection sets Bearer authentication once at the top, and every request inherits it except request 7, which is set to No Auth on purpose.

How the signing script works

This is request 7's pre-request script:

// Builds a signed URL from this request's query params. Type values unencoded.
const esc = (v) => encodeURIComponent(v)
  .replace(/[!'()*]/g, (c) => '%' + c.charCodeAt(0).toString(16).toUpperCase())
  .replace(/%20/g, '+'); // CGI.escape rules, as the API expects
const params = {};
pm.request.url.query.each((p) => {
  if (!p.disabled && !['signature', 'expires', 'key_id'].includes(p.key)) {
    params[p.key] = pm.variables.replaceIn(p.value);
  }
});
params.key_id = pm.variables.get('keyId');
params.expires = String(Math.floor(Date.now() / 1000) + 3600); // 1 hour; the maximum is 30 days
const query = Object.entries(params).map(([k, v]) => `${esc(k)}=${esc(v)}`).sort().join('&');
const cryptoJs = require('crypto-js');
const signature = cryptoJs.HmacSHA256(query, pm.variables.get('secretKey')).toString(cryptoJs.enc.Hex);
pm.request.url = `${pm.variables.get('baseUrl')}/v1/screenshot?${query}&signature=${signature}`;
pm.collectionVariables.set('lastSignedUrl', pm.request.url.toString());

It reads the enabled query parameters from the request (url, preset, and anything you add), adds key_id and an expires one hour ahead, and signs them with the rules from the signed URL docs: each name and value escaped like Ruby's CGI.escape, pairs sorted, joined with &, then HMAC-SHA256 with the secret key. It then replaces the request URL with the signed one. Two practical notes:

  • Type parameter values unencoded. The script encodes them. A value you already encoded would be encoded twice and the signature would not match the URL the API sees.
  • Only GET parameters can be signed. Signed URLs work only for GET (or HEAD) requests to /v1/screenshot, so options without a flat GET name, such as wait.for_selector, are not available here. Use request 3 for those.

Parameter mapping between GET and POST

Requests 1, 2 and 7 use flat query parameters; request 3 uses nested JSON. The two map one to one:

GET query POST JSON
width, height viewport.width, viewport.height
full_page=true capture.mode: "full_page"
format, quality output.format, output.quality
delay, timeout wait.delay, wait.timeout
block_ads, block_cookies block.ads, block.cookie_banners
cache_available cache.available

The full list is in the GET reference.

Errors and retries

Postman does not retry. A failed request shows the status and the JSON error in the Body tab, and the test results tell you which check failed.

Status and code Meaning What to change
401 missing_credentials No key, or an unknown key Check that the environment is selected and apiKey is filled
401 invalid_signature Request 7 signed different bytes than it sent Remove manual encoding from query values; check secretKey
401 expired_signature The URL is older than its expires Send request 7 again to sign a fresh one
422 timeout The target page did not load in time Add timeout=60 or delay; retryable is true
429 too_many_requests The per-minute limit, shared by all keys on the account Wait for the time in Retry-After

Every code has its own page under error codes.

Limits and costs

  • Credits. Requests 1 to 4 and 7 render pages and cost a credit each on a fresh render. Request 2 sends cache_available=true, so sending it again is free while the stored render is valid. Requests 5, 6 and 8 cost nothing. See pricing for the credits in each plan.
  • Runner and newman. A collection run sends all eight requests in a row. On the Free plan's limit of 10 requests a minute that is fine for one run, but not for many iterations; add a delay between requests in the runner settings.
  • Batch size. Request 4 sends two URLs. The maximum depends on your plan; see rate limits.

Troubleshooting

  • Request 1's target URL has its own query string. Without signing, Postman sends the value as typed, and & inside it starts a new parameter. Select the value and use EncodeURIComponent from the right-click menu, or use request 3, where the URL is a JSON string.
  • Request 5 returns 404. Run request 4 first; it saves batchId.

To go further, run the same calls from code with the quick start, put request 2 into a Pipedream code step, or move to batch jobs with the batch endpoint reference.

Frequently asked questions

Why does Postman show garbage text for the first request?
The API returns PNG bytes by default, and Postman shows an image preview only in the Body tab's preview mode. Request 2 sends Accept: application/json, so you get a readable JSON body with a CDN link instead.
Can Postman sign URLs without a server?
Yes. Request 7 signs the query string in a pre-request script with HMAC-SHA256 and your rs_secret_ key, which stays in your Postman environment. The signed URL it builds is safe to paste into a browser or an img tag until it expires.
Can I run the collection in CI?
Yes. Every request has tests, so newman run with the collection and environment files exits with a non-zero code when a request fails. Point targetUrl at a page you own.

Related in this topic

Try RenderScreenshot free

150 free credits every month, no credit card required.