ScreenshotAPI

Elixir

Use ScreenshotAPI from Elixir with the Req HTTP client.

Overview

These examples use Req, the modern HTTP client for Elixir. Add it to your mix.exs:

defp deps do
  [{:req, "~> 0.5"}]
end

Quick Start

Set your API key

export SCREENSHOTAPI_KEY="sk_live_your_key_here"

Take a screenshot

api_key = System.get_env("SCREENSHOTAPI_KEY")

{:ok, response} =
  Req.get("https://screenshotapi.to/api/v1/screenshot",
    params: [url: "https://example.com"],
    headers: [{"x-api-key", api_key}]
  )

if response.status == 200 do
  File.write!("screenshot.png", response.body)

  credits = Req.Response.get_header(response, "x-credits-remaining") |> List.first()
  IO.puts("Credits remaining: #{credits}")
else
  IO.puts(:stderr, "Error #{response.status}: #{response.body}")
end

Client Module

A reusable client with all screenshot options:

defmodule ScreenshotAPI do
  @base_url "https://screenshotapi.to"

  defstruct [:api_key, base_url: @base_url]

  defmodule Result do
    defstruct [:content, :content_type, :credits_remaining, :screenshot_id, :duration_ms]
  end

  def new(api_key, opts \\ []) do
    %__MODULE__{
      api_key: api_key,
      base_url: Keyword.get(opts, :base_url, @base_url)
    }
  end

  def capture(%__MODULE__{} = client, url, opts \\ []) do
    params =
      [url: url]
      |> maybe_add(:width, opts[:width])
      |> maybe_add(:height, opts[:height])
      |> maybe_add(:fullPage, if(opts[:full_page], do: "true"))
      |> maybe_add(:type, opts[:format])
      |> maybe_add(:quality, opts[:quality])
      |> maybe_add(:colorScheme, opts[:color_scheme])
      |> maybe_add(:waitUntil, opts[:wait_until])
      |> maybe_add(:waitForSelector, opts[:wait_for_selector])
      |> maybe_add(:delay, opts[:delay])

    case Req.get("#{client.base_url}/api/v1/screenshot",
           params: params,
           headers: [{"x-api-key", client.api_key}]
         ) do
      {:ok, %{status: 200} = resp} ->
        {:ok,
         %Result{
           content: resp.body,
           content_type: get_header(resp, "content-type") || "image/png",
           credits_remaining: get_header(resp, "x-credits-remaining") |> to_int(),
           screenshot_id: get_header(resp, "x-screenshot-id") || "",
           duration_ms: get_header(resp, "x-duration-ms") |> to_int()
         }}

      {:ok, resp} ->
        {:error, "Screenshot failed (#{resp.status}): #{inspect(resp.body)}"}

      {:error, reason} ->
        {:error, "Request failed: #{inspect(reason)}"}
    end
  end

  defp maybe_add(params, _key, nil), do: params
  defp maybe_add(params, key, value), do: Keyword.put(params, key, value)

  defp get_header(resp, name) do
    Req.Response.get_header(resp, name) |> List.first()
  end

  defp to_int(nil), do: 0
  defp to_int(val) when is_binary(val), do: String.to_integer(val)
end

Usage

api = ScreenshotAPI.new(System.get_env("SCREENSHOTAPI_KEY"))

{:ok, result} =
  ScreenshotAPI.capture(api, "https://github.com",
    width: 1280,
    height: 720,
    format: "webp",
    quality: 85
  )

File.write!("github.webp", result.content)
IO.puts("Credits remaining: #{result.credits_remaining}")
IO.puts("Duration: #{result.duration_ms}ms")

Common Patterns

Batch Screenshots

Capture multiple URLs concurrently with Task.async_stream:

urls = [
  "https://example.com",
  "https://github.com",
  "https://news.ycombinator.com"
]

urls
|> Enum.with_index()
|> Task.async_stream(fn {url, i} ->
  case ScreenshotAPI.capture(api, url) do
    {:ok, result} ->
      File.write!("screenshot-#{i}.png", result.content)
      IO.puts("✓ #{url}")

    {:error, reason} ->
      IO.puts(:stderr, "✗ #{url}: #{reason}")
  end
end, max_concurrency: 5)
|> Stream.run()

Phoenix Controller

Serve screenshots in a Phoenix application:

defmodule MyAppWeb.ScreenshotController do
  use MyAppWeb, :controller

  @api ScreenshotAPI.new(System.get_env("SCREENSHOTAPI_KEY"))

  def show(conn, %{"url" => url}) do
    case ScreenshotAPI.capture(@api, url, format: "webp", quality: 80) do
      {:ok, result} ->
        conn
        |> put_resp_content_type(result.content_type)
        |> put_resp_header("cache-control", "public, max-age=3600")
        |> send_resp(200, result.content)

      {:error, _reason} ->
        conn
        |> put_status(502)
        |> json(%{error: "Screenshot failed"})
    end
  end

  def show(conn, _params) do
    conn
    |> put_status(400)
    |> json(%{error: "url is required"})
  end
end

GenServer for Rate Limiting

Wrap the client in a GenServer with built-in rate limiting:

defmodule ScreenshotWorker do
  use GenServer

  def start_link(api_key) do
    GenServer.start_link(__MODULE__, api_key, name: __MODULE__)
  end

  def capture(url, opts \\ []) do
    GenServer.call(__MODULE__, {:capture, url, opts}, 30_000)
  end

  @impl true
  def init(api_key) do
    {:ok, %{api: ScreenshotAPI.new(api_key)}}
  end

  @impl true
  def handle_call({:capture, url, opts}, _from, state) do
    result = ScreenshotAPI.capture(state.api, url, opts)
    {:reply, result, state}
  end
end

Error Handling

case ScreenshotAPI.capture(api, "https://example.com") do
  {:ok, result} ->
    File.write!("screenshot.png", result.content)

  {:error, message} when message =~ "402" ->
    IO.puts(:stderr, "Out of credits — purchase more at screenshotapi.to")

  {:error, message} when message =~ "403" ->
    IO.puts(:stderr, "Invalid API key — check your configuration")

  {:error, message} ->
    IO.puts(:stderr, "Screenshot failed: #{message}")
end

On this page