自由にできる非公式なMackerelクライアントライブラリを持っとくと便利そうな予感を得たので、まずは素朴にちょいと素振りして傾向と対策の勘所を。

取り合えずサービスの上3つからお試し。

mackerel.io

open System
open System.Net
open System.Net.Http
open System.Text
open System.Text.Json

type NewService = { name: string; memo: string }

type Service =
    { name: string
      memo: string
      roles: string list }

type Services = { services: Service list }

let baseUrl = "https://api.mackerelio.com"
let key = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

let run task =
    task
    |> Async.AwaitTask
    |> Async.RunSynchronously
    |> function
        | Ok res -> printfn "%A" res
        | Error err -> printfn "%s" err

let getServices (baseUrl: string) (key: string) =
    task {
        let uri = Uri $"{baseUrl}/api/v0/services"
        use request = new HttpRequestMessage(HttpMethod.Get, uri)
        request.Headers.Add("X-Api-Key", key)
        use httpClient = new HttpClient()
        let! response = httpClient.SendAsync request

        return!
            match response.StatusCode with
            | HttpStatusCode.OK ->
                task {
                    let! content = response.Content.ReadAsStreamAsync()
                    let! services = JsonSerializer.DeserializeAsync<Services>(content)
                    return Ok services
                }
            | e -> task { return Error $"Unexpected response: {e}" }
    }

getServices baseUrl key |> run

let createService (baseUrl: string) (key: string) (service: NewService) =
    task {
        let json = JsonSerializer.Serialize service
        use content = new StringContent(json, Encoding.UTF8, "application/json")
        let uri = Uri $"{baseUrl}/api/v0/services"
        use request = new HttpRequestMessage(HttpMethod.Post, uri, Content = content)
        request.Headers.Add("X-Api-Key", key)
        use httpClient = new HttpClient()
        let! response = httpClient.SendAsync request

        return!
            match response.StatusCode with
            | HttpStatusCode.OK ->
                task {
                    let! content = response.Content.ReadAsStreamAsync()
                    let! service = JsonSerializer.DeserializeAsync<Service>(content)
                    return Ok service
                }
            | HttpStatusCode.BadRequest -> task { return Error "Invalid service name" }
            | HttpStatusCode.Forbidden -> task { return Error "Unpermitted request" }
            | e -> task { return Error $"Unexpected response: {e}" }
    }

{ name = "Service99"; memo = "めも" }
|> createService baseUrl key
|> run

let deleteService (baseUrl: string) (key: string) (serviceName: string) =
    task {
        use content = new StringContent("", Encoding.UTF8, "application/json")
        let uri = Uri $"{baseUrl}/api/v0/services/{serviceName}"
        use request = new HttpRequestMessage(HttpMethod.Delete, uri, Content = content)
        request.Headers.Add("X-Api-Key", key)
        use httpClient = new HttpClient()
        let! response = httpClient.SendAsync request

        return!
            match response.StatusCode with
            | HttpStatusCode.OK ->
                task {
                    let! content = response.Content.ReadAsStreamAsync()
                    let! service = JsonSerializer.DeserializeAsync<Service>(content)
                    return Ok service
                }
            | HttpStatusCode.Forbidden -> task { return Error "Unpermitted request" }
            | HttpStatusCode.NotFound -> task { return Error "Service not found" }
            | e -> task { return Error $"Unexpected response: {e}" }
    }

"Service99" |> deleteService baseUrl key |> run

サービスの削除でボディ無しでもContent-Type: application/jsonが無いとBad Request返ってくるのにちょっとハマった。

なるほど把握した。ボチボチやってこ。