The single error type every FoPost call answers with.
The API replies to a failed request with {"error": "<code>", "message": "<text>"},
which maps onto :code and :message. :body keeps the decoded body as it arrived so
fields this struct does not model stay reachable.
Elixir does not need one exception module per status code — the status is on the struct and the predicates below cover the cases callers actually branch on:
case FoPost.Posts.publish(client, post.id) do
{:ok, result} ->
result
{:error, %FoPost.Error{} = error} ->
cond do
FoPost.Error.rate_limited?(error) -> retry_in(error.retry_after)
FoPost.Error.payment_required?(error) -> send_to(error.upgrade_url)
true -> Logger.error(Exception.message(error))
end
endA transport failure — connection refused, DNS, a timeout — is the same struct with
status: nil and code: "transport_error", so a caller never has to match on two
shapes.
The struct is also an exception, so raise error and the bang variants of every
resource function work the way they do elsewhere in Elixir.
Summary
Functions
True for a 403 — the key is valid but lacks the scope or the workspace.
True for a 404 — no such resource, or it sits outside this key's reach.
True for a 402 — no active subscription, or AI credits are exhausted.
True for a 429. :retry_after holds the wait the API asked for, in seconds.
True for any 5xx.
True when the request never produced a response: connection refused, DNS, a timeout.
True for a 401 — the API key is missing, malformed, or revoked.
True for a 400 or 422 — the request body did not validate.
Types
@type t() :: %FoPost.Error{ __exception__: true, body: term(), code: String.t() | nil, message: String.t() | nil, retry_after: pos_integer() | nil, status: pos_integer() | nil, upgrade_url: String.t() | nil }
Functions
True for a 403 — the key is valid but lacks the scope or the workspace.
True for a 404 — no such resource, or it sits outside this key's reach.
True for a 402 — no active subscription, or AI credits are exhausted.
:upgrade_url carries where to send the user when the API supplies one.
True for a 429. :retry_after holds the wait the API asked for, in seconds.
The client already retries a 429 twice on its own, so seeing this means the retries were used up.
True for any 5xx.
True when the request never produced a response: connection refused, DNS, a timeout.
True for a 401 — the API key is missing, malformed, or revoked.
True for a 400 or 422 — the request body did not validate.