Low-level Graph API client for the WhatsApp Business Cloud API: authenticated JSON, multipart, and binary requests, response unwrapping, and URL building.

Per-call credentials. Every API-calling function in this package takes an optional trailing opts Map, resolved by resolve: opts first, then context variables, then defaults. Nothing from opts is written to ctx, so one run can act for many businesses and parallel flows don't share credentials. An access-token from opts is masked in call records. An access-token present but blank or null is an error, never a fallback to whatsapp.access.token.

  • access-token: the access token for this call
  • phone-number-id: the business number to use when a request has no phone_number_id
  • api-version: the Graph API version for this call

Context Vars

Optional
  • whatsapp.access.token
  • whatsapp.api.version

Values

USER_AGENT

USER_AGENT: Str "hot-whatsapp/1.1.0"

Functions

auth-headers

fn (access-token: Str): Map

download

fn (url: Str): Bytes
fn (url: Str, access-token: Str): Bytes
fn (url: Str, opts: Map): Bytes

Authenticated GET returning the raw response bytes (Bytes) on 2xx, or an Err carrying HttpError. Used for media downloads.

Context Vars: whatsapp.access.token

error-message

fn (error: Any): Str?

The human-readable message from a failed call's error value: Meta's body.error.message when the body is a Graph error object, otherwise the stringified body. Returns null for a non-error value.

Example

sent ::whatsapp::messages/send-text(::whatsapp::messages/SendTextRequest({
  phone_number_id: "109876543210", to: "15551234567", text: "Hi"
}))
if-err(sent, (e) { println(::whatsapp::api/error-message(e)) })

handle-response

fn (response: Result): Any

Unwrap an HTTP response: the decoded body on 2xx, an Err carrying HttpError otherwise. Callers return transport Errs before calling it. Exposed for testing.

non-blank

fn (value: Any): Str?

request

fn (method: Str, url: Str): Any
fn (method: Str, url: Str, opts: Map): Any
fn (method: Str, url: Str, additional-headers: Map, body: Map): Any
fn (method: Str, url: Str, additional-headers: Map, body: Map, access-token: Str): Any
fn (method: Str, url: Str, additional-headers: Map, body: Map, opts: Map): Any

Call the Graph API with a JSON body. The access token comes from per-call opts (see resolve), the whatsapp.access.token context variable, or the explicit 5-arity form, and is sent as a Bearer token. Null fields in body are not sent. Returns the decoded body on 2xx, or an Err carrying HttpError.

Example

::api ::whatsapp::api

// GET request
profile ::api/request("GET", ::api/url("109876543210/whatsapp_business_profile"))

// POST request with JSON body
sent ::api/request("POST", ::api/url("109876543210/messages"), {}, {messaging_product: "whatsapp", to: "15551234567", type: "text", text: {body: "Hello"}})

// With per-call credentials
tenant {access-token: "tenant-b-token"}
other ::api/request("GET", ::api/url("109876543210/whatsapp_business_profile", tenant), tenant)
Context Vars: whatsapp.access.token

request-bytes

fn (method: Str, url: Str, content-type: Str, body: Bytes): Any
fn (method: Str, url: Str, content-type: Str, body: Bytes, access-token: Str): Any
fn (method: Str, url: Str, content-type: Str, body: Bytes, opts: Map): Any

Send a raw body (e.g. a multipart upload built as Bytes) with an explicit Content-Type. Returns the decoded JSON body on 2xx, or an Err carrying HttpError.

Context Vars: whatsapp.access.token

resolve

fn (opts: Map): Map

Credentials for one call (see resolve-from). An access-token from opts is masked in call records with ::hot::ctx/mark-secret, and opts values are never written to ctx. The API version comes from ::whatsapp::core/api-version(opts).

Example

::whatsapp::api/resolve({access-token: "tenant-b-token", phone-number-id: "109876543210"}).phone-number-id
Context Vars: whatsapp.access.token

resolve-from

fn (opts: Map, ctx-values: Map): Map

The one resolver, pure: {access-token} for a call from opts first, then ctx-values (a Map of context variables), plus phone-number-id when opts has one. An Err when no token is set, or when access-token is in opts but blank or null (that never falls back to context). Exposed for testing; resolve feeds it the context.

segment

fn (value: Any): Str

Percent-encode one path segment (an id or name). Exposed for testing.

send-json

fn (method: Str, url: Str, additional-headers: Map, body: Map, access-token: Str): Any

url

fn (path: Str): Str
fn (path: Str, opts: Map): Str

Build a Graph API URL from a path relative to the versioned base, e.g. url("123/messages") gives https://graph.facebook.com/v26.0/123/messages. The version follows ::whatsapp::core/api-version, with opts when given.

with-phone-number

fn (phone-number-id: Str?, opts: Map, action: Fn): Any

Run action with the URL-encoded business number for a call: phone-number-id when set, else phone-number-id in opts. An Err when neither is set.

Types

HttpError

HttpError type {
    status: Int,
    headers: Map,
    body: Any
}

A non-2xx Graph API response. Meta's error object is in body.error (message, type, code, error_subcode, fbtrace_id); error-message extracts the message safely.