# `TypeSafeAPI.HTTP`
[🔗](https://github.com/typesend/typesafe_ai/blob/v0.1.0-alpha.3/lib/typesafe_api/http.ex#L1)

Layer 1: the raw HTTP client. Maps in, maps out.

This module knows about URLs, headers, JSON, retries, telemetry, and how
status codes map to `TypeSafeAPI.Error` types. It knows nothing about questions
or answers. If the API grows a field tomorrow, this layer passes it through
untouched, which is the point: the typed layer above can lag the API without
the raw layer becoming useless.

    client = TypeSafeAPI.new(api_key: "sk-...")

    TypeSafeAPI.HTTP.post(client, "/v1/systemone", %{
      "state" => "Help! My payouts have been failing for 3 days.",
      "model" => "jev-latest",
      "questions" => %{"urgent" => %{"type" => "noul", "instructions" => "Is it urgent?"}}
    })
    #=> {:ok, %{"model" => "jev-latest", "answers" => %{...}, "usage" => %{...}}}

JSON is encoded with Elixir's built-in `JSON` module. Bodies may contain any
term with a `JSON.Encoder` implementation, which is how the typed layer keeps
Choice options in caller order.

## Timeouts and connection pools

`Req` starts (or reuses) a Finch pool keyed by the connection settings of a
request, so anything that changes those settings from call to call creates a
pool per distinct value and throws connection reuse away.

What selects a pool:

  * the client's `finch:` option - names a pool you started and supervise
    yourself, which is the only way to control pool size. `connect_options`
    is then omitted entirely, `connect_timeout` included: those settings
    belong to your `Finch` child spec instead
  * `req_options` carrying `connect_options:` - a different value is a
    different pool. What this module sets as the connect timeout is merged
    *under* yours, so `connect_timeout` survives unless you set `timeout:`
    inside `connect_options` yourself
  * the client's `connect_timeout`, which is the `connect_options` timeout
    this module sets; it is fixed for the life of a client

What does not select a pool:

  * the client's `timeout` and the per-call `:timeout`, which means the same
    thing in `TypeSafeAPI.evaluate_many/4`; they set `receive_timeout` only,
    so two calls with different timeouts share a pool
  * `:retry`, `:telemetry`, headers, the body, the path

In short: vary `:timeout` per call as much as you like; set `connect_options`
once, on the client, or hand the client a `finch:` pool of your own.

# `call_option`

```elixir
@type call_option() ::
  {:timeout, pos_integer()}
  | {:retry, TypeSafeAPI.Retry.t() | keyword()}
  | {:req_options, keyword()}
  | {:telemetry, map()}
```

Per-call options accepted by `post/4` and `get/3`.

  * `:timeout` - overrides the client timeout for this call (milliseconds).
    Bounds waiting for the response only; the connect timeout is the
    client's `connect_timeout`, so this option never changes which Finch
    pool the call uses
  * `:retry` - a keyword list layers onto the client retry policy, changing
    only the settings it names; a `%TypeSafeAPI.Retry{}` replaces it wholesale
  * `:req_options` - extra `Req` options merged in last. The options this
    library owns (`:retry`, `:auth`, `:base_url`, `:finch`) are not yours to
    set here; use the matching client option
  * `:telemetry` - extra metadata merged into the telemetry events

# `full_response`

```elixir
@type full_response() ::
  {:ok, TypeSafeAPI.HTTP.Response.t()} | {:error, TypeSafeAPI.Error.t()}
```

Result of `request/5`: the full response, not just the body.

# `response`

```elixir
@type response() :: {:ok, map()} | {:error, TypeSafeAPI.Error.t()}
```

# `build`

```elixir
@spec build(TypeSafeAPI.Client.t(), :get | :post, String.t(), term(), [call_option()]) ::
  Req.Request.t()
```

Builds the `Req.Request` for a call without running it.

Exposed for inspection and for `TypeSafeAPI.Test`; most callers want `post/4`.

The per-call `:timeout` lands on `receive_timeout`; `connect_options` comes
from the client alone, so two calls that differ only in `:timeout` build
identical `connect_options` and share one connection pool.

Connection settings and `retry: false` are merged after every user option,
so `req_options` can neither re-enable `Req`'s own retry step on top of this
library's nor drop the client's connect timeout.

# `get`

```elixir
@spec get(TypeSafeAPI.Client.t(), String.t(), [call_option()]) :: response()
```

Sends a `GET` and returns the decoded JSON body.

# `post`

```elixir
@spec post(TypeSafeAPI.Client.t(), String.t(), term(), [call_option()]) :: response()
```

Sends a JSON `POST` and returns the decoded JSON body.

# `request`

```elixir
@spec request(TypeSafeAPI.Client.t(), :get | :post, String.t(), term(), [
  call_option()
]) ::
  full_response()
```

Sends a request and returns the full `TypeSafeAPI.HTTP.Response`, including the
status, headers, `request_id`, and how many retries it took.

`body` is JSON-encoded when present; pass `nil` for a bodiless request.

# `user_agent`

```elixir
@spec user_agent() :: String.t()
```

The `User-Agent` sent with every request.

---

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