# `PostHog.API.Client`
[🔗](https://github.com/posthog/posthog-elixir/blob/v2.18.0/lib/posthog/api/client.ex#L1)

Behaviour and the default implementation of a PostHog API client. Uses `Req`.

The default client sends request bodies gzip-compressed and retries on transient
failures. If you need different behaviour — for example, to disable compression,
add custom headers, attach telemetry, or use a different HTTP library — you can
implement this behaviour in your own module and configure it via `api_client_module`.

## Using the default client directly

    > client = PostHog.API.Client.client("phc_abcdedfgh", "https://us.i.posthog.com")
    %PostHog.API.Client{
      client: %Req.Request{...},
      module: PostHog.API.Client
    }

    > client.module.request(client.client, :post, "/flags", json: %{distinct_id: "user123"}, params: %{v: 2, config: true})
    {:ok, %Req.Response{status: 200, body: %{...}}}

## Writing a custom client

Implement the `client/2` and `request/4` callbacks, then set the config:

    config :posthog, api_client_module: MyApp.PostHogClient

### Wrapping the default client

The easiest approach is to delegate to the default client and override only what
you need. For example, to disable gzip compression:

    defmodule MyApp.PostHogClient do
      @behaviour PostHog.API.Client

      @impl true
      def client(api_key, api_host) do
        default = PostHog.API.Client.client(api_key, api_host)
        # Remove the compress_body step added by the default client
        custom = Req.merge(default.client, compress_body: false)
        %{default | client: custom}
      end

      @impl true
      defdelegate request(client, method, url, opts), to: PostHog.API.Client
    end

### Adding custom request headers

    defmodule MyApp.PostHogClient do
      @behaviour PostHog.API.Client

      @impl true
      def client(api_key, api_host) do
        default = PostHog.API.Client.client(api_key, api_host)
        custom = Req.merge(default.client, headers: [{"x-custom-header", "value"}])
        %{default | client: custom}
      end

      @impl true
      defdelegate request(client, method, url, opts), to: PostHog.API.Client
    end

### Using a different HTTP library

You can skip `Req` entirely and use any HTTP client. The `client` term you return
is opaque — it's passed back to your `request/4` callback as-is.

NOTE: The code below is not guaranteed to be correct or complete — it's just illustrative of the general approach.

    defmodule MyApp.FinchPostHogClient do
      @behaviour PostHog.API.Client

      @impl true
      def client(api_key, api_host) do
        %PostHog.API.Client{
          client: %{api_key: api_key, api_host: api_host},
          module: __MODULE__
        }
      end

      @impl true
      def request(client, method, url, opts) do
        body = opts[:json] |> Map.put_new(:api_key, client.api_key) |> Jason.encode!()

        Finch.build(method, client.api_host <> url, [{"content-type", "application/json"}], body)
        |> Finch.request(MyApp.Finch)
        |> case do
          {:ok, %Finch.Response{status: status, body: body}} ->
            {:ok, %{status: status, body: Jason.decode!(body)}}

          {:error, exception} ->
            {:error, exception}
        end
      end
    end

# `client`

```elixir
@type client() :: any()
```

Arbitrary term that is passed as the first argument to the `c:request/4` callback.

For the default client, this is a `t:Req.Request.t/0` struct.

# `response`

```elixir
@type response() ::
  {:ok, %{status: non_neg_integer(), body: any()}} | {:error, Exception.t()}
```

Response tuple returned by `c:request/4`.

Successful responses must expose at least a numeric `:status` and decoded
`:body`; they may also expose response `:headers`. Errors should return the
exception or error struct from the HTTP client.

# `t`

```elixir
@type t() :: %PostHog.API.Client{
  client: client(),
  feature_flags_request_max_retries: non_neg_integer(),
  module: atom()
}
```

Wrapper returned by `c:client/2` and stored in `t:PostHog.Config.config/0`.

- `:client` - opaque client state passed back to `c:request/4`.
- `:module` - module implementing this behaviour.

# `client`

```elixir
@callback client(api_key :: String.t(), cloud :: String.t()) :: t()
```

Creates a struct that encapsulates all information required for making requests to PostHog's public endpoints.

# `request`

```elixir
@callback request(
  client :: client(),
  method :: atom(),
  url :: String.t(),
  opts :: keyword()
) :: response()
```

Sends an API request.

Things such as the API token are expected to be baked into the `client` argument.

# `user_agent`

The User-Agent header value sent with every API request.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
