Elixir
The Elixir SDK is an idiomatic Hex package emitted from the same GlottoIR as every other target.
It is functional and module-first: the public package facade builds the client with
Petstore.new/2, and each resource gets its own module whose functions take that struct as their
first argument — Petstore.Pets.list_pets(client). It is built on Req.
Quickstart
def deps do
[
{:petstore, "~> 1.0"}
]
end
client =
Petstore.new("https://api.petstore.example",
token: System.get_env("PETSTORE_TOKEN")
)
# calls return {:ok, _} / {:error, %Petstore.ApiError{}} tuples
{:ok, pet} = Petstore.Pets.create_pet(client, %{name: "Rex"})
# paginated functions return a lazy Stream
Petstore.Pets.list_pets(client)
|> Enum.each(&IO.puts(&1.name))
Typed models
Each GlottoIR model becomes a struct module with from_map/to_map; functions decode and return
the typed response and accept a typed request body. Discriminated unions resolve to the right
variant.
Typed errors
Functions return a %Glotto.ApiError{} struct carrying the parsed error body, with
Glotto.ApiError.kind/1 mapping the status to a documented atom so callers case on :not_found
rather than matching status codes. See Errors.
Pagination
Paginated list functions return a lazy Stream that walks every page as you enumerate, advancing
the cursor for you. Manual-page companions expose typed items, the full response, and explicit next-page navigation. See Pagination.
Retries & backoff
Transient failures (5xx, 429, transport errors) retry with exponential backoff and jitter,
configurable per client and per call. A trailing keyword list accepts request headers, timeout, retry count, idempotency and applicable extra query/body values; explicit values override configured method/resource defaults. Terminating the request-owning Task cancels its request and retry wait. See Retries & timeouts.
SSE streaming
Server-sent-event endpoints return a Stream of typed events decoded from the text/event-stream
framing. See Streaming and Authentication.
Unknown response fields
Your API can add a response field without it being a breaking change — but a generated struct has only the keys it declares, and its from_map/1 factory reads only those. The generated
models keep it instead: a field the SDK wasn’t generated from is retained on decode, readable
through an accessor, and written back out when the model is re-serialized.
{:ok, pet} = Glotto.Pets.create_pet(client, body)
# A field your API started returning after this SDK was generated.
species = Glotto.Pet.extra_fields(pet)["species"]
# Re-encoding preserves it — a read-modify-write never silently drops it.
json = pet |> Glotto.Pet.to_map() |> Jason.encode!()
extra_fields/1 returns a plain map, so nested objects and lists survive intact, and retention is
recursive.
The struct key defaults to an empty map, so %Glotto.Pet{id: "p1"} still builds.
The retained fields are read-only by design. To send a field your spec doesn’t model yet, use the per-call extra-body escape hatch rather than writing to the retained bag.
File uploads
multipart/form-data operations build the multipart body from their fields for you; an
application/octet-stream operation takes a positional body (a binary) sent raw with the right
content-type.
Binary downloads
Declared binary responses return an owned Glotto.BinaryDownload with status, headers, media type, content length, filename and request ID. Consume its chunks/1 stream, call read_all/2 with a required max_bytes bound, or pipe/2 to a binary IO device or callback. Limits count delivered bytes, including decoded gzip bytes. Close an abandoned response explicitly; terminating its original request Task also closes it. The request timeout covers the first byte or EOF, and no retry occurs after delivery. See File transfers.