typesafe ai sdk
September 18, 2026 · View on GitHub
typesafeai sdk in elixir using req based on:
https://github.com/typesafe-ai/typesafe-sdk-js
Usage
Set TYPESAFE_API_KEY in your environment, then create and use the client:
{:ok, client} = TypeSafe.new()
{:ok, response} =
TypeSafe.system_one(client, %{
state: %{document: "I was charged twice. Please fix this ASAP."},
questions: %{
category: TypeSafe.choice("What is this ticket about?", %{billing: nil, technical: nil, other: nil})
}
})
response["answers"]["category"]["choice"]
TypeSafe.new/1 also accepts api_key:, base_url:, default_model:, and
log_level: options, each falling back to its TYPESAFE_* environment
variable and then a default — see the @doc on TypeSafe.new/1.
Question helpers TypeSafe.noul/0,1,2, TypeSafe.choice/2, and
TypeSafe.score/2 build the typed questions passed to system_one/3.
TypeSafe.list_models/2 lists available models.
Errors
A non-2xx HTTP response from system_one/3 or list_models/2 returns a
status-mapped error struct under TypeSafe.Error.*:
TypeSafe.Error.BadRequest(400)TypeSafe.Error.Authentication(401)TypeSafe.Error.PermissionDenied(403)TypeSafe.Error.NotFound(404)TypeSafe.Error.UnprocessableEntity(422)TypeSafe.Error.RateLimit(429, carriesretry_after_ms)TypeSafe.Error.InternalServer(5xx, including 529)TypeSafe.Error.API(any other non-2xx status, e.g. 409 or 418)TypeSafe.Error.Connection(a transport failure — closed socket, DNS, TLS)TypeSafe.Error.Timeout(the configured timeout was exceeded)
The generic TypeSafe.Error still covers everything that isn't a mapped
HTTP failure: client-config problems from new/1, question-validation
failures from system_one/3, and an unexpected /v1/models response
shape.
There is no shared base struct — the ten TypeSafe.Error.* structs and
the generic TypeSafe.Error are unrelated exceptions. Match on {:error, exception} for a catch-all clause; Exception.message/1 works on all of
them:
case TypeSafe.system_one(client, request) do
{:ok, result} ->
result
{:error, %TypeSafe.Error.Authentication{} = error} ->
{:error, "auth failed: " <> Exception.message(error)}
{:error, %TypeSafe.Error.RateLimit{retry_after_ms: ms} = error} ->
retry = if ms, do: " (retry after #{ms}ms)", else: ""
{:error, "rate limited#{retry}: " <> Exception.message(error)}
{:error, exception} ->
{:error, Exception.message(exception)}
end
Known limitations
- No retries. A 429 or 503 response returns immediately; the caller is
responsible for any retry loop.
Retry-Afteris exposed only asTypeSafe.Error.RateLimit.retry_after_ms, and only when the header is an integer-seconds value — decimals,retry-after-ms, and HTTP-date values are not parsed. - Fixed timeout. Every request uses a fixed 10-second timeout; there is no client-level or per-call timeout option yet.
- No logging.
- No telemetry.
Installation
This package is not on Hex — the name typesafe_sdk there belongs to a
different project. Install it as a git dependency:
def deps do
[
{:typesafe_sdk_ex, github: "vinnie357/typesafe_sdk_ex"}
]
end
Development
Tooling is managed via mise:
mise install
mise run ci
mise run ci runs the full local quality gate: compile with warnings as
errors, format check, credo --strict, the test suite, mix hex.audit,
and a gitleaks scan.
See CONTRIBUTING.md for the full contribution workflow.