Usage (for humans and agents)

Copy Markdown View Source

Short reference for integrating this package. Prefer this page + the DocRaptor moduledoc over guessing from HTTP examples on docraptor.com.

Entrypoint

  1. {:ok, client} = DocRaptor.Client.new(api_key: key) — :test defaults to true
  2. Pick a create function (table below)
  3. Match on {:ok, result} | {:error, %DocRaptor.Error{}} — no bang APIs, and %DocRaptor.Error{} is not an exception (no defexception; do not raise it)

The client is a plain struct (not a GenServer). Building per call or storing in app config/state are both fine.

Which create function?

NeedCallSuccess typeNotes
PDF/XLS bytes nowDocRaptor.create_doc/2%DocRaptor.Document{body: binary()}Sync ~60s (receive_timeout); retry: false
Long / background jobcreate_async_doc/2 then await_async_doc/3 or poll / webhook%AsyncJob{} → %Document{}await blocks with Process.sleep — use a worker, not a LiveView/controller process
Public download URL (paid add-on)create_hosted_doc/2%Status{download_url, download_id}Not raw bytes
Hosted + backgroundcreate_hosted_async_doc/2%AsyncJob{}Prefer status/download_url; await_async_doc hits /download and may not match hosted billing intent

Custom HTTP: build %Req.Request{}, then DocRaptor.Client.attach/2, then Req.request/1.

Document attrs (doc_attrs)

Accepted as a map or keyword list. Atom or string keys both work. Nested prince_options keys are stringified. This client validates only:

FieldRequired?Notes
nameyes (client)Non-empty string. DocRaptor treats name as optional; we require it.
document_type or typeyes (client)Must be present; value is to_string’d and sent as-is. DocRaptor expects pdf / xls / xlsx. If both keys are set, document_type is used.
document_content xor document_urlyesExactly one; both → :invalid_document
testnoOverrides client default (true)

For Excel, send spreadsheet XML in document_content (or a URL to XML) with document_type: :xls or :xlsx. PDF content is HTML.

Everything else is passed through to DocRaptor JSON, including:

  • javascript, ignore_console_messages, pipeline, referrer, tag, strict, help
  • callback_url (async webhook)
  • hosted_download_limit, hosted_expires_at (hosted)
  • prince_options map — common keys: media ("print" \| "screen"), baseurl, encrypt, disallow_print, javascript, max_passes, passwords, etc.

Full upstream list: https://docraptor.com/documentation/api/parameters

JavaScript engines (don't confuse them)

FlagEngineUse when
javascript: trueDocRaptor’s engineCharts, Typekit, most page JS (default choice)
prince_options: %{javascript: true}Prince’s engineCanvas / Prince PDF JS APIs

Enabling both runs JS twice — usually avoid. pipeline selects Prince/JS runtime versions; ignore_console_messages: true keeps console.log from failing the job (defaults vary by pipeline on DocRaptor’s side).

Async IDs

create_async_*  →  %AsyncJob{status_id}
       │
       ▼
get_async_doc_status(client, status_id)
       │
       ├─ :queued | :working  → poll again
       ├─ :failed             → error (validation_errors || message)
       └─ :completed          → download_id (and maybe download_url if hosted)
                                    │
                                    ▼
                         get_async_doc(client, download_id)  # non-hosted bytes

await_async_doc/3 does status polling + get_async_doc/2 for you. Options: :interval_ms (default 1000), :timeout_ms (default 600_000).

callback_url webhook

On successful async completion only, DocRaptor POSTs your callback_url (form parameters). On generation failure the callback is not called — poll /status instead.

ParamMeaning
download_urlURL to fetch the finished document
download_idPer DocRaptor: corresponds to the create response status_id

Retries: if your endpoint does not return 2xx within ~10s, DocRaptor retries the callback up to three times.

# Phoenix controller sketch
def docraptor_callback(conn, %{"download_url" => url, "download_id" => id}) do
  # enqueue worker to fetch url / record id — respond 2xx quickly
  json(conn, %{ok: true})
end

Errors

%DocRaptor.Error{reason: reason, message: msg, status: http_or_nil, body: raw}
reasonWhenstatusbody
:missing_api_keyClient.new/1nilnil
:invalid_documentLocal encode validationniloften nil
:apiNon-2xx or bad JSON shapeHTTP codeusually a binary (often XML with <error> tags); sometimes a map if already decoded
:transportNetwork / Req failurenilunderlying reason
:timeoutawait_async_doc/3 deadlinenillast %Status{}
:async_failedAsync status failednil%Status{}; message prefers validation_errors then message

For :api, :message is derived from the body: <error>...</error> segments joined with newlines when present, otherwise the raw body string (or inspect/1 for maps).

Common HTTP statuses you may see on :api (upstream; not exhaustive): 401 auth, 422 validation, 429 rate limit, 5xx server errors.

Retries

Client.attach/2 sets retry: false. This package does not auto-retry. If you retry at the app layer:

  • Worth retrying (with backoff): 429, transient 5xx, :transport
  • Usually do not retry: 401, 422, :invalid_document, :async_failed
case DocRaptor.create_doc(client, attrs) do
  {:ok, %DocRaptor.Document{body: pdf}} ->
    {:ok, pdf}

  {:error, %DocRaptor.Error{reason: :invalid_document, message: msg}} ->
    {:error, msg}

  {:error, %DocRaptor.Error{reason: :api, status: status, message: msg}}
  when status in [429, 500, 502, 503, 504] ->
    {:retry, {status, msg}}

  {:error, %DocRaptor.Error{} = err} ->
    {:error, err}
end

Pitfalls

  • Localhost: DocRaptor cannot fetch private hosts. Prefer inlined document_content (CSS/assets included). Relative URLs need prince_options.baseurl or absolute URLs.
  • Test mode: client default test: true → watermarked PDF / Excel capped at 20 rows / hosted limited to 5 downloads and 1-day expiry. Production needs test: false on client or per document.
  • JavaScript: off by default — see engine table above.
  • Prince watermarks (@prince-overlay / flow): DocRaptor’s tutorials use test: false for these samples (their public test key / test mode is not sufficient for that tutorial path).
  • Sync timeout: ~60s receive timeout, no Req retries — heavy docs → async.
  • Hosted: paid add-on; expire with DocRaptor.expire/2 when done.

Phoenix + background jobs

# config/runtime.exs
config :my_app, :docraptor_api_key, System.fetch_env!("DOCRAPTOR_API_KEY")

# request path — sync only for fast docs
api_key = Application.fetch_env!(:my_app, :docraptor_api_key)
{:ok, client} = DocRaptor.Client.new(api_key: api_key, test: false)

{:ok, %DocRaptor.Document{body: pdf, content_type: ct}} =
  DocRaptor.create_doc(client, %{
    name: "invoice-#{id}.pdf",
    document_type: :pdf,
    document_content: html
  })

conn
|> put_resp_content_type(ct || "application/pdf")
|> send_resp(200, pdf)

Slow docs — Oban-style worker (blocking await is OK inside the worker):

def perform(%Oban.Job{args: %{"html" => html, "name" => name}}) do
  api_key = Application.fetch_env!(:my_app, :docraptor_api_key)
  {:ok, client} = DocRaptor.Client.new(api_key: api_key, test: false)

  with {:ok, %DocRaptor.AsyncJob{status_id: id}} <-
         DocRaptor.create_async_doc(client, %{
           name: name,
           document_type: :pdf,
           document_content: html
         }),
       {:ok, %DocRaptor.Document{body: pdf}} <-
         DocRaptor.await_async_doc(client, id) do
    # store pdf / upload to S3 / email — then :
    :ok
  end
end

Or pass callback_url: "https://example.com/webhooks/docraptor" on create and handle the webhook instead of awaiting.

Testing

Use a distinct Req.Test stub name per test module/process to avoid cross-talk:

{:ok, client} =
  DocRaptor.Client.new(
    api_key: "test",
    req_options: [plug: {Req.Test, __MODULE__.DocRaptorStub}]
  )

Minimal patterns

Sync PDF

{:ok, client} = DocRaptor.Client.new(api_key: System.fetch_env!("DOCRAPTOR_API_KEY"))

{:ok, %DocRaptor.Document{body: pdf, num_pages: pages}} =
  DocRaptor.create_doc(client, %{
    name: "invoice.pdf",
    document_type: :pdf,
    document_content: html
  })

JS + print media

DocRaptor.create_doc(client, %{
  name: "chart.pdf",
  document_type: :pdf,
  document_content: html_with_scripts,
  javascript: true,
  prince_options: %{media: "print"}
})

Async await

{:ok, %DocRaptor.AsyncJob{status_id: id}} =
  DocRaptor.create_async_doc(client, attrs)

{:ok, %DocRaptor.Document{body: pdf}} =
  DocRaptor.await_async_doc(client, id, interval_ms: 1_000, timeout_ms: 600_000)