featurenotavailable

The request asks for something that your current plan does not include. Your key is valid and you may have credits left, but this one part of the request is not allowed on the plan. Remove that part, or change the plan. This page is part of the error code reference.

HTTP status 403 Forbidden
error.type authorization_error
error.retryable false
Credits used 0

Example response

{
  "error": {
    "type": "authorization_error",
    "code": "feature_not_available",
    "message": "Custom storage is not included in your plan",
    "retryable": false,
    "docs": "https://docs.renderscreenshot.com/errors/feature_not_available"
  },
  "request_id": "req_abc123"
}

What depends on the plan

Almost nothing. Every rendering option (formats, presets, devices, full page, PDF, blocking, wait options, content checks) is in every plan, the Free plan included. The plan changes three things:

Item How the plan affects it
Custom storage (copy each screenshot to your own S3-compatible bucket) Paid plans only
Batch size Larger on higher plans. Too many URLs in one batch is a different code: batch_size_exceeded.
Rate and concurrency limits Higher on higher plans. Going over them gives a 429, not this error. See rate limits.

So on the Free plan, a storage block is the usual cause of feature_not_available.

Common causes

  • A storage block on the Free plan. Code or a config file copied from a paid account includes "storage": { ... }.
  • A downgrade. The account moved from a paid plan to Free, and a job still sends the storage options.
  • A shared client wrapper. One helper adds storage options to every request, including requests from a project on a different account.

How to fix it

  1. Read error.message. It names the feature.
  2. Decide if you need that feature for this request:
    • If yes, upgrade on the pricing page.
    • If no, remove the option from the request.
  3. If you only need a stable link to the image, you may not need custom storage at all. The X-Cache-URL header gives a public CDN link on every plan.
  4. Do not retry the same request. It fails the same way until the request or the plan changes.

Fall back without the feature

This Python function asks for custom storage. If the plan does not include it, the function sends the request again without the storage block and keeps the image locally. The second request is a new request, not a retry of the failed one.

# storage_fallback.py (Python 3.10+, requests)
import os
import sys

import requests

API = os.environ.get("RS_BASE_URL", "https://api.renderscreenshot.com")
HEADERS = {"Authorization": f"Bearer {os.environ['RS_API_KEY']}"}


def post(body: dict) -> requests.Response:
    return requests.post(f"{API}/v1/screenshot", json=body, headers=HEADERS, timeout=(10, 90))


def capture(url: str) -> tuple[bytes, bool]:
    """Returns (image, copied_by_api). The API copies to your bucket in the background."""
    body = {"url": url, "preset": "og_card", "storage": {"enabled": True}}
    res = post(body)

    if res.status_code == 403 and res.json()["error"]["code"] == "feature_not_available":
        print(f"storage not in plan ({res.json()['request_id']}); saving locally", file=sys.stderr)
        body.pop("storage")
        res = post(body)
        res.raise_for_status()
        return res.content, False

    res.raise_for_status()
    return res.content, True


if __name__ == "__main__":
    image, copied = capture(sys.argv[1])
    if not copied:
        with open("shot.png", "wb") as f:
            f.write(image)
    print(f"{len(image)} bytes, copied to bucket by API: {copied}")

The same idea with the Ruby SDK, which accepts the request as a Hash:

require 'renderscreenshot'

client = RenderScreenshot.client(ENV.fetch('RS_API_KEY'))
request = { url: 'https://example.com', preset: 'og_card', storage: { enabled: true } }

begin
  image = client.take(request)
rescue RenderScreenshot::AuthorizationError => e
  raise unless e.code == 'feature_not_available'

  warn "#{e.message} (#{e.request_id}); capturing without storage"
  image = client.take(request.except(:storage))
  File.binwrite('shot.png', image) # keep your own copy instead
end

Log the fallback. A silent fallback hides the fact that screenshots are no longer in your bucket.

Tested on 2026-10-15 with Python 3.14 plus requests 2.34.2, and Ruby 4.0.7 plus the renderscreenshot gem 1.0.0, against two local mocks: one that refuses storage (as on Free) and one that accepts it. The fallback ran only against the first.

Related in this topic

Was this page helpful?