3.1.0 (2026-08-14)

Failures that used to crash now come back as errors: commands return an error tuple for a response they do not recognize, FritzApi.Client.login/3 refuses the unauthenticated session ID instead of handing back an unusable client, and FritzApi.Error messages render for every reason rather than only for atoms.

Check your device function names

Two of the device function classes reported in FritzApi.Actor were misspelled and are now correct. If you match on either string, update it:

  • "0AVM DECT Repeater" is now "AVM DECT Repeater"
  • "AVM- Button" is now "AVM-Button"

The function list is also ordered by bit position now, which moves "AVM DECT Repeater" earlier for devices that report it.

Fixed

  • Commands no longer raise when the FritzBox returns a response they do not recognize. They return {:error, %FritzApi.Error{reason: {:unexpected_response, body}}}, which is what their specs always promised. Previously these escaped as FunctionClauseError, MatchError or CaseClauseError. A FritzBox with no paired devices was enough to trigger it.
  • FritzApi.Client.login/3 fails with {:error, %FritzApi.Error{reason: :login_failed}} instead of returning a client holding the unauthenticated session ID "0000000000000000". Such a client failed every later call with a misleading :user_not_authorized.
  • FritzApi.Client.login/3 reports a login response it does not recognize as {:error, %FritzApi.Error{reason: {:unexpected_response, body}}}. A response without a SessionInfo element used to escape as a CaseClauseError.
  • Exception.message/1 on a FritzApi.Error no longer raises for the most common reasons. A transport error struct returned by the HTTP client or a {:login_failed, block_time: 60} tuple used to produce a Protocol.UndefinedError blob in logs and stack traces.
  • FritzApi.get_switch_list/1 returns {:ok, []} for a FritzBox without switches, instead of {:ok, [""]}. FritzApi.get_device_list_infos/1 likewise returns {:ok, []} for an empty device list.
  • A malformed XML response is reported as {:error, %FritzApi.Error{reason: {:unexpected_response, body}}}. The XML parser throws, so a truncated response or a captive portal page served as text/xml used to escape as an uncaught {:error, ~c"Malformed: ..."} throw that no case or with at the call site could catch.
  • FritzApi.get_device_list_infos/1 decodes values it does not recognize as nil instead of raising. A single unexpected switch state or a non-numeric functionbitmask used to fail the whole device list.
  • Two device function classes were misspelled; see the note above.
  • FritzApi.Actor typed id as String.t() while it is parsed into an integer, and fields that decode to nil for an empty XML element were typed as non-nil. FritzApi.set_hkr_target_temperature/3 was specced as 8..28, an integer range that excludes the 21.5 in its own example, and its documentation claimed it returns {:ok, temperature} rather than :ok. The radiator controller getters listed :unknown among their return values, which they never returned.
  • The FritzApi.HTTPClient documentation showed an example implementing a different library's behaviour and a request/5 callback that does not exist.
  • The README and FritzApi examples showed a device list the library cannot produce: a string id, and plain maps where FritzApi.Powermeter, FritzApi.Switch and FritzApi.Temperature structs are returned.

Added

Changed

  • The minimum Elixir version is now 1.15, up from 1.11. Finch and its dependencies require it, so the built-in HTTP client could not run on an older Elixir anyway.
  • FritzApi.Client.t/0 is no longer @opaque. It never was in practice, and Dialyzer reported an opacity violation for every FritzApi command. The struct fields remain private; use FritzApi.Client.session_id/1 to read the session ID.
  • FritzApi.HTTPClient.method/0 and FritzApi.HTTPClient.params/0 were removed. Neither was used by a callback.
  • FritzApi.HTTPClient.child_spec/1 may return a list of child specifications in addition to a single one or nil, for clients that need more than one supervised process.
  • The check for a missing :finch dependency moved from the application callback into FritzApi.HTTPClient.Finch, so the error now names the module that needs it.

3.0.0 (2023-08-12)

Breaking Changes

  • Migrate built-in HTTP from hackney to Finch
  • Replace the:adapter with the :client option

Upgrade instructions

Dependencies

FritzApi now ships with an HTTP client based on :finch instead of :hackney.

Add :finch to your list of dependencies in mix.exs:

def deps do
  [
    {:fritz_api, "~> 3.0"},
    {:finch, "~> 0.16"},
  ]
end

HTTP client (optional)

  1. Remove the :adapter configuration from FritzApi.Client.new/1:

    {:ok, client} = FritzApi.Client.new(
    -  adapter: {Tesla.Adapter.Gun, []}
    )
  2. In config/runtime.exs set the :fritz_api, :client option and to your own module that implements the FritzApi.HTTPClient behaviour:

    + config :fritz_api,
    +   client: MyGunAdapter

See the documentation for FritzApi.HTTPClient for more information.

2.2.0 (2022-12-29)

  • Fix deprecation warning
  • Update dependencies

2.1.0 (2022-02-22)

  • Bump elixir_xml_to_map to 3.0

2.0.0 (2020-11-27)

FritzApi 2.0 is a major release containing significant changes, particularly around the FritzApi.Client.

Enhancements

  • Use tesla to make the underlying HTTP client configurable

Breaking Changes

  • Replace the stateful FritzApi.Client that would need to be started as part of a supervision tree with a simpler struct based approach:
    • Call FritzApi.Client.new() to create a new client and FritzApi.Client.login(client, "user", "password") to authenticate with the Fritz API
    • See README for an example
  • Switch functions like FritzApi.get_switch_state/2 return :on, :off atoms instead of a boolean, and :unknown if the actor is unavailable
  • Introduce a custom error struct (FritzApi.Error) and use custom structs for actors (e.g. FritzApi.Switch)
  • Make hackney an optional dependency. To use the default hackney based adapter, add {:hackney, "~> 1.16"} to the list of dependencies.