Skip to documentation content

Elixir SDK

A supervised client with tuple errors and telemetry events.

In this guide
  1. 1Understand the package contract
  2. 2Run or resume jobs
  3. 3Handle native errors
  4. 4Verify webhooks
In this guide
  1. 1Understand the package contract
  2. 2Run or resume jobs
  3. 3Handle native errors
  4. 4Verify webhooks

Package contract

The planned Hex package is :nordic_devhouse. A supervised client owns transport state, returns tagged tuples, and emits :telemetry events without requiring Phoenix.

DistributionNamespace or importRuntime
:nordic_devhouseNordicDevhouseSupported Elixir and OTP release lines

Create and reuse the client

Add the named client to the supervision tree and pass its name to resource modules. Run returns {:ok, result} or {:error, error}; retry and polling stay inside the client process.

Elixir
children = [
  {NordicDevhouse.Client,
   name: MyApp.NordicDevhouse,
   api_key: System.fetch_env!("NORDIC_DEVHOUSE_API_KEY"),
   max_retries: 3}
]

Supervisor.start_link(children, strategy: :one_for_one)

{:ok, result} =
  NordicDevhouse.Scrape.run(MyApp.NordicDevhouse, %{
    url: "https://example.com/products",
    formats: [:json],
    idempotency_key: "catalog-2026-08-11"
  })

IO.inspect({result.job_id, result.records})

Control a durable job

Start and wait expose the durable job ID for Oban and other workflows. Caller timeouts stop waiting but do not terminate the server-side job.

Elixir
{:ok, job} =
  NordicDevhouse.Scrape.start(MyApp.NordicDevhouse, %{
    url: "https://example.com/products",
    formats: [:json]
  })

{:ok, result} =
  NordicDevhouse.Jobs.wait(
    MyApp.NordicDevhouse,
    job.id,
    timeout: :timer.minutes(10)
  )

Public models

Names follow the language conventions, but every SDK preserves the same fields and job-state semantics.

ConceptContract
Client optionsAPI key, base URL, request timeout, retry budget, and optional test transport
Scrape requestURL, output formats, webhook URL, metadata, and idempotency key
JobStable ID, status, timestamps, progress, and warnings
Job resultJob ID, record count, typed data, metadata, and request ID
Webhook eventVerified event ID, type, timestamp, and typed data

Handle errors idiomatically

Failures return {:error, %NordicDevhouse.Error{}} with kind, code, request_id, retry_after, status, and cause. Exceptional exits are reserved for invalid process configuration.

Elixir
case NordicDevhouse.Scrape.run(client, request) do
  {:ok, result} ->
    consume(result)

  {:error, %{kind: :rate_limited} = error} ->
    Logger.warning("rate limited",
      request_id: error.request_id,
      retry_after: error.retry_after)

  {:error, %{kind: :validation, issues: issues}} ->
    reject(issues)

  {:error, error} ->
    Logger.error("SDK failure", request_id: error.request_id)
end

Verify the raw webhook body

Verification uses the exact request bytes, validates HMAC-SHA256 and timestamp tolerance, and returns a typed event. Persist the verified event ID to prevent duplicate processing.

Elixir
with {:ok, event} <-
       NordicDevhouse.Webhooks.verify(
         raw_body,
         List.first(get_req_header(conn, "nordic-signature")),
         System.fetch_env!("NORDIC_WEBHOOK_SECRET")
       ) do
  import_result(event.data.job_id)
end

Runtime and lifecycle rules

  1. 1
    Run the client under a supervisor with a stable registered name
  2. 2
    Return tagged tuples and structured exception-free request errors
  3. 3
    Emit documented :telemetry start, stop, retry, and exception events
  4. 4
    Keep Phoenix and Oban integrations optional
  5. 5
    Accept a behaviour-based transport adapter only at the internal test seam
Was this page helpful?