Packages

Higher-level Elixir SDK for Polymarket: Gamma market discovery, Data API, CTF/pUSD helpers, and convenience facades. Depends on polymarket_clob for the low-level CLOB surface.

Current section

Files

Jump to
polymarket_sdk lib polymarket http.ex
Raw

lib/polymarket/http.ex

defmodule Polymarket.HTTP do
@moduledoc """
Generic Req-based transport helper for the Polymarket SDK.
This module is the shared HTTP layer used by `Polymarket.Gamma`,
`Polymarket.Data`, and any future read-mostly Polymarket public-API
surface in this package. User code should prefer the higher-level
modules; this is documented mainly so it is testable in isolation and
so the response contract is recorded once instead of repeated in each
consumer.
This module deliberately does not depend on `polymarket_clob`'s HTTP
layer. The two have different host defaults and different error
semantics: `polymarket_clob`'s `HTTP.normalize/2` returns
`%PolymarketClob.Error{}` structs, while this SDK's wrappers return
flat tuples. Keeping the layers separate avoids leaking the lower
package's namespace into this package's user-facing types.
## Response contract
Every call returns one of:
* `{:ok, body}` — 2xx, body is the JSON-decoded response (Req's
built-in `:decode_body` step handles JSON automatically when the
response carries `content-type: application/json`).
* `{:error, {:http_error, status, body}}` — non-2xx HTTP response.
`status` is the integer status code; `body` is the upstream payload
(typically a JSON map, sometimes a raw string).
* `{:error, {:transport_error, reason}}` — Req-level transport
failure (DNS, TCP, TLS, connection timeout, HTTP-2 protocol error).
`reason` is the underlying exception or atom.
## Options
* `:params` — keyword list serialized into the query string (default `[]`).
* `:plug` — Req.Test plug, for tests.
* `:timeout` — `receive_timeout` in milliseconds (default `15_000`).
* `:retry` — Req retry policy. Defaults to `:transient` for GETs and
`false` for everything else, mirroring `polymarket_clob`'s posture.
* `:max_retries` — Req max retries (default `2`).
* `:headers` — extra request headers (default `[]`).
"""
@type result :: {:ok, term()} | {:error, term()}
@default_timeout 15_000
@default_max_retries 2
@doc """
Issues a GET against `host <> path` and returns the normalized result.
"""
@spec get(String.t(), String.t(), keyword()) :: result()
def get(host, path, opts \\ []) do
request(:get, host, path, opts)
end
# Generic dispatch is private for now: only GET-based callers ship
# today. When a non-GET caller lands, lift this to public.
defp request(method, host, path, opts) do
url = url(host, path)
req_opts =
[
method: method,
url: url,
params: Keyword.get(opts, :params, []),
headers: Keyword.get(opts, :headers, []),
receive_timeout: Keyword.get(opts, :timeout, @default_timeout),
retry: Keyword.get(opts, :retry, default_retry(method)),
max_retries: Keyword.get(opts, :max_retries, @default_max_retries)
]
|> maybe_put(:plug, Keyword.get(opts, :plug))
req_opts
|> Req.request()
|> normalize()
end
defp normalize({:ok, %{status: status, body: body}}) when status in 200..299 do
{:ok, body}
end
defp normalize({:ok, %{status: status, body: body}}) do
{:error, {:http_error, status, body}}
end
defp normalize({:error, reason}) do
{:error, {:transport_error, reason}}
end
defp default_retry(:get), do: :transient
defp default_retry(_), do: false
defp url(host, path) do
String.trim_trailing(host, "/") <> "/" <> String.trim_leading(path, "/")
end
defp maybe_put(opts, _key, nil), do: opts
defp maybe_put(opts, key, value), do: Keyword.put(opts, key, value)
end