Repository F# setup
open System
open System.Net.Http
open System.Threading
open System.Threading.Tasks
open Axial
open Axial.HttpClient
open Axial.PlatformServiceRequests
Interpolated URLs Encode Every Hole
String-built URLs break on spaces, slashes, and user input. The DSL builders treat every interpolation hole as one URL-encoded value:
> open Axial.HttpClient;;
> open Axial.HttpClient.DSL;;
> let name = "a b/c&d" in GET $"https://api.example.com/users/{name}" |> Request.render;;val it: string = "GET https://api.example.com/users/a%20b%2Fc%26d"AxialHttpClientAxial.HttpClient.DSLname: stringGET: FormattableString -> HttpRequestCreates a GET request from an interpolated URL. Every hole is URL-encoded as one value. <example><code>GET $"https://api.example.com/users/{userId}"</code></example>
(|>): 'T1 -> ('T1 -> 'U) -> 'UApply a function to a value, the value being on the left, the function on the right The argument. The function. The function result. let doubleIt x = x * 2 3 |> doubleIt // Evaluates to 6
Axial.HttpClient.RequestModulerender: HttpRequest -> stringRenders the redacted request line used in error transcripts. <example><code>Request.render request</code></example>
HEAD, POST, PUT, PATCH, and DELETE work the same way. A hole cannot smuggle in extra path segments or
query parameters; only the literal text of the template controls URL structure.
When a URL is already a complete string with no inserted values, use the plain builders:
> Axial.HttpClient.Http.get "https://api.example.com/users" |> Axial.HttpClient.Request.render;;val it: string = "GET https://api.example.com/users"Axialget: string -> HttpRequestCreates a GET request. <example><code>Http.get "https://api.example.com/users"</code></example>
HttpClientAxial.HttpClient.HttpModule(|>): 'T1 -> ('T1 -> 'U) -> 'UApply a function to a value, the value being on the left, the function on the right The argument. The function. The function result. let doubleIt x = x * 2 3 |> doubleIt // Evaluates to 6
render: HttpRequest -> stringRenders the redacted request line used in error transcripts. <example><code>Request.render request</code></example>
Axial.HttpClient.RequestModuleQuery Parameters
query appends one URL-encoded name-value pair. Values are formatted with the invariant culture, so numbers and
dates are safe to pass directly:
> open Axial.HttpClient;;
> open Axial.HttpClient.DSL;;
> GET $"https://api.example.com/search" |> query "q" "f# & http" |> query "page" 2 |> Request.render;;val it: string = "GET https://api.example.com/search?q=f%23%20%26%20http&page=2"AxialHttpClientAxial.HttpClient.DSLGET: FormattableString -> HttpRequestCreates a GET request from an interpolated URL. Every hole is URL-encoded as one value. <example><code>GET $"https://api.example.com/users/{userId}"</code></example>
(|>): 'T1 -> ('T1 -> 'U) -> 'UApply a function to a value, the value being on the left, the function on the right The argument. The function. The function result. let doubleIt x = x * 2 3 |> doubleIt // Evaluates to 6
query: string -> 'a -> HttpRequest -> HttpRequestAppends a URL-encoded query parameter. <example><code>GET $"{root}/search" |> query "q" term</code></example>
Axial.HttpClient.RequestModulerender: HttpRequest -> stringRenders the redacted request line used in error transcripts. <example><code>Request.render request</code></example>
Secrets Never Reach Diagnostics
API keys and tokens must not appear in logs, error messages, or plans. Three tools keep them out:
> open Axial.HttpClient;;
> open Axial.HttpClient.DSL;;
> let apiKey = "s3cret" in GET $"https://api.example.com/lookup?key={secret apiKey}" |> Request.render;;
> Http.get "https://api.example.com/lookup" |> Request.secretQuery "api_key" "s3cret" |> Request.render;;
> (Http.get "https://api.example.com/me" |> bearer "token" |> Request.plan).Headers;;
> (Http.get "https://api.example.com/me" |> basicAuth "ada" "password" |> Request.plan).Headers;;val it: string = "GET https://api.example.com/lookup?key=***"
val it: string = "GET https://api.example.com/lookup?api_key=***"
val it: Tuple<string,string> list = [("Authorization", "***")]
val it: Tuple<string,string> list = [("Authorization", "***")]AxialHttpClientAxial.HttpClient.DSLapiKey: stringGET: FormattableString -> HttpRequestCreates a GET request from an interpolated URL. Every hole is URL-encoded as one value. <example><code>GET $"https://api.example.com/users/{userId}"</code></example>
(|>): 'T1 -> ('T1 -> 'U) -> 'UApply a function to a value, the value being on the left, the function on the right The argument. The function. The function result. let doubleIt x = x * 2 3 |> doubleIt // Evaluates to 6
Axial.HttpClient.RequestModulerender: HttpRequest -> stringRenders the redacted request line used in error transcripts. <example><code>Request.render request</code></example>
Axial.HttpClient.HttpModuleget: string -> HttpRequestCreates a GET request. <example><code>Http.get "https://api.example.com/users"</code></example>
secretQuery: string -> obj -> HttpRequest -> HttpRequestAppends a query parameter whose value is replaced with <c>***</c> in plans and error transcripts. <example><code>request |> Request.secretQuery "api_key" key</code></example>
Headers: (string * string) listbearer: string -> HttpRequest -> HttpRequestAdds a redacted bearer-token Authorization header. <example><code>request |> bearer token</code></example>
plan: HttpRequest -> RequestPlanReturns a redacted request plan without sending anything. <example><code>Request.plan request</code></example>
basicAuth: string -> string -> HttpRequest -> HttpRequestAdds a redacted basic-auth Authorization header. <example><code>request |> basicAuth user password</code></example>
A secret interpolation hole renders as *** in every transcript. A secret query parameter is sent for real and
rendered as key=***. bearer and basicAuth are always redacted, with no opt-in needed.
Request.render produces the redacted request line (for example GET https://api.example.com/lookup?key=***)
that appears inside HttpError values, so error logging is safe by default.
Redaction is opt-in per value. An ordinary query or header value is sent for real and rendered in full in
plans and transcripts; use secretQuery / secretHeader (or a secret interpolation hole) for API keys, tokens,
and other sensitive values. Body helpers (jsonBody, textBody, bytesBody, formBody) send the real content,
but Request.plan shows only the content type and size, never the body text. That keeps body contents out of
plans, but the value is still transmitted — do not treat a body as redacted just because the plan omits its text.
Headers
> open Axial.HttpClient;;
> open Axial.HttpClient.DSL;;
> (Http.get "https://api.example.com/users" |> header "Accept" "application/json" |> Request.userAgent "my-app/1.0" |> Request.secretHeader "X-Api-Key" "s3cret" |> Request.plan).Headers |> List.map snd;;val it: string list = ["application/json"; "my-app/1.0"; "***"]AxialHttpClientAxial.HttpClient.DSLHeaders: (string * string) listAxial.HttpClient.HttpModuleget: string -> HttpRequestCreates a GET request. <example><code>Http.get "https://api.example.com/users"</code></example>
(|>): 'T1 -> ('T1 -> 'U) -> 'UApply a function to a value, the value being on the left, the function on the right The argument. The function. The function result. let doubleIt x = x * 2 3 |> doubleIt // Evaluates to 6
header: string -> string -> HttpRequest -> HttpRequestAppends one request header. <example><code>request |> header "Accept" "text/csv"</code></example>
Axial.HttpClient.RequestModuleuserAgent: string -> HttpRequest -> HttpRequestSets the User-Agent header. <example><code>request |> Request.userAgent "axial-app/1.0"</code></example>
secretHeader: string -> string -> HttpRequest -> HttpRequestAppends a header whose value is replaced with <c>***</c> in plans and error transcripts. <example><code>request |> Request.secretHeader "X-Api-Key" key</code></example>
plan: HttpRequest -> RequestPlanReturns a redacted request plan without sending anything. <example><code>Request.plan request</code></example>
Microsoft.FSharp.Collections.ListModuleContains operations for working with values of type . Operations for collections such as lists, arrays, sets, maps and sequences. See also F# Collection Types in the F# Language Guide.
map: ('T -> 'U) -> 'T list -> 'U listBuilds a new collection whose elements are the results of applying the given function to each of the elements of the collection. The function to transform elements from the input list. The input list. The list of transformed elements. let inputs = [ "a"; "bbb"; "cc" ] inputs |> List.map (fun x -> x.Length) Evaluates to [ 1; 3; 2 ] This is an O(n) operation, where n is the length of the list.
snd: 'T1 * 'T2 -> 'T2Return the second element of a tuple, snd (a,b) = b. The input tuple. The second value. snd ("first", 2) // Evaluates to 2
Request.acceptJson is shorthand for the JSON accept header; fetchJson and Http.getJson add it for you.
Bodies
Each body helper sets the content type. Request.plan shows it with the body's size:
> open Axial.HttpClient;;
> open Axial.HttpClient.DSL;;
> (POST $"https://api.example.com/users" |> jsonBody """{"name":"Ada"}""" |> Request.plan).Body;;
> let encodeName (name: string) = $"{{\"name\":\"{name}\"}}";;
> (POST $"https://api.example.com/users" |> jsonBodyOf encodeName "Ada" |> Request.plan).Body;;
> (POST $"https://api.example.com/notes" |> textBody "hello" |> Request.plan).Body;;
> (POST $"https://api.example.com/search" |> formBody [ "q", "axial"; "page", "2" ] |> Request.plan).Body;;
> (POST $"https://api.example.com/blobs" |> Request.bytesBody "application/octet-stream" [| 1uy; 2uy |] |> Request.plan).Body;;val it: string = "application/json (14 characters)"
val it: string = "application/json (14 characters)"
val it: string = "text/plain (5 characters)"
val it: string = "form (2 fields)"
val it: string = "application/octet-stream (2 bytes)"AxialHttpClientAxial.HttpClient.DSLBody: stringPOST: FormattableString -> HttpRequestCreates a POST request from an interpolated URL with encoded holes. <example><code>POST $"https://api.example.com/users" |> jsonBody payload</code></example>
(|>): 'T1 -> ('T1 -> 'U) -> 'UApply a function to a value, the value being on the left, the function on the right The argument. The function. The function result. let doubleIt x = x * 2 3 |> doubleIt // Evaluates to 6
jsonBody: string -> HttpRequest -> HttpRequestSends an already-serialized JSON body. <example><code>request |> jsonBody """{"name":"Ada"}"""</code></example>
Axial.HttpClient.RequestModuleplan: HttpRequest -> RequestPlanReturns a redacted request plan without sending anything. <example><code>Request.plan request</code></example>
encodeName: string -> stringname: stringstringAn abbreviation for the CLI type . Basic Types
jsonBodyOf: ('a -> string) -> 'a -> HttpRequest -> HttpRequestEncodes and sends a JSON body. <example><code>request |> jsonBodyOf (Json.serialize codec) user</code></example>
textBody: string -> HttpRequest -> HttpRequestSends a plain-text body. <example><code>request |> textBody "hello"</code></example>
formBody: (string * string) list -> HttpRequest -> HttpRequestSends URL-encoded form fields. <example><code>request |> formBody [ "q", "axial" ]</code></example>
bytesBody: string -> byte array -> HttpRequest -> HttpRequestSends raw bytes with an explicit content type. <example><code>request |> Request.bytesBody "application/octet-stream" payload</code></example>
jsonBodyOf takes any 'value -> string function, so it works with Reified's Json module (Reified.Schema), hand-written serializers, or
any other JSON library without coupling this package to one.
Plans Show What Would Be Sent
Request.plan returns a redacted, serializable description without performing any I/O, for logging,
dry runs, and approval flows:
Show page setup
// Setup for the checked examples on this page.
open System
open Axial.HttpClient
/// Fails the docs test when an example's result differs from the value shown.
let shouldEqual expected actual =
if actual <> expected then failwithf "Expected %A but got %A" expected actualHttp.post "https://api.example.com/users"
|> Request.bearer "token"
|> Request.jsonBody """{"name":"Ada"}"""
|> Request.timeout (TimeSpan.FromSeconds 5.0)
|> Request.plan
|> shouldEqual
{ Method = "POST"
Url = "https://api.example.com/users"
Headers = [ "Authorization", "***" ]
Body = "application/json (14 characters)"
Timeout = Some(TimeSpan.FromSeconds 5.0)
MaxResponseBytes = None
Expectation = "2xx" }
Axial.HttpClient.HttpModulepost: string -> HttpRequestCreates a POST request. <example><code>Http.post "https://api.example.com/users"</code></example>
(|>): 'T1 -> ('T1 -> 'U) -> 'UApply a function to a value, the value being on the left, the function on the right The argument. The function. The function result. let doubleIt x = x * 2 3 |> doubleIt // Evaluates to 6
Axial.HttpClient.RequestModulebearer: string -> HttpRequest -> HttpRequestAdds a bearer-token Authorization header. The token is always redacted in diagnostics. <example><code>request |> Request.bearer token</code></example>
jsonBody: string -> HttpRequest -> HttpRequestSends an already-serialized JSON body with the <c>application/json</c> content type. <example><code>request |> Request.jsonBody """{"name":"Ada"}"""</code></example>
timeout: TimeSpan -> HttpRequest -> HttpRequestSets a per-request timeout enforced by the live service. <example><code>request |> Request.timeout (TimeSpan.FromSeconds 5.0)</code></example>
System.TimeSpanRepresents a time interval.
FromSeconds: float -> TimeSpanReturns a that represents a specified number of seconds, where the specification is accurate to the nearest millisecond. A number of seconds, accurate to the nearest millisecond. An object that represents . is less than or greater than . -or- is . -or- is . is equal to .
plan: HttpRequest -> RequestPlanReturns a redacted request plan without sending anything. <example><code>Request.plan request</code></example>
shouldEqual: 'a -> 'a -> unitMethod: stringUrl: stringHeaders: (string * string) listBody: stringTimeout: TimeSpan optionSomeThe representation of "Value of type 'T" The input value. An option representing the value.
MaxResponseBytes: int64 optionNoneThe representation of "No value"
Expectation: stringRequires & types
Requires: open Axial.HttpClient; open System (packages: Axial.HttpClient)When Not To Use The DSL
Open Axial.HttpClient.DSL locally in modules that make HTTP calls, not at the top of every file: it introduces
short names such as query, header, and timeout. In code that only forwards a request built elsewhere, the
qualified Request.* functions keep the origin obvious.

