API

Better Auth Ruby exposes the same endpoint handlers through Rack and through a server-side Ruby API object.

config/auth.rb
require "better_auth"

auth = BetterAuth.auth(
  base_url: "http://localhost:3000/api/auth",
  secret: ENV.fetch("BETTER_AUTH_SECRET"),
  email_and_password: {enabled: true}
)

Calling API Endpoints On The Server

Every endpoint is available on auth.api with a Ruby snake_case method name.

result = auth.api.sign_in_email(
  body: {
    email: "john@example.com",
    password: "password123"
  }
)

session_token = result[:token]

Plugin endpoints are exposed the same way after the plugin is configured.

auth = BetterAuth.auth(
  secret: ENV.fetch("BETTER_AUTH_SECRET"),
  plugins: [BetterAuth::Plugins.magic_link]
)

auth.api.sign_in_magic_link(
  body: {
    email: "john@example.com",
    callbackURL: "/dashboard"
  }
)

Body, Headers, Query

Use body: for request bodies, query: for URL query parameters, params: for path parameters, and headers: for request headers.

auth.api.send_verification_email(
  body: {
    email: "john@example.com",
    callbackURL: "/dashboard"
  }
)

auth.api.verify_email(
  query: {
    token: params[:token],
    callbackURL: "/dashboard"
  }
)

auth.api.get_session(
  headers: {
    "cookie" => request.get_header("HTTP_COOKIE").to_s
  }
)

Request bodies accept the upstream camelCase keys and the Ruby snake_case aliases where the route supports both.

auth.api.change_password(
  headers: {"cookie" => cookie_header},
  body: {
    current_password: "old-password",
    new_password: "new-password",
    revoke_other_sessions: true
  }
)

Getting headers And Rack Response Object

Getting headers

Use return_headers: true when you want the endpoint payload and response headers.

result = auth.api.sign_in_email(
  body: {
    email: "john@example.com",
    password: "password123"
  },
  return_headers: true
)

cookie = result[:headers].fetch("set-cookie")

Getting Response Object

Use as_response: true when you need the raw Rack tuple.

status, headers, body = auth.api.sign_in_email(
  body: {
    email: "john@example.com",
    password: "password123"
  },
  as_response: true
)

json = JSON.parse(body.join)

This is useful for tests, controller integration, and flows that need to forward Set-Cookie headers.

Error Handling

Server-side calls raise BetterAuth::APIError for endpoint errors.

begin
  auth.api.sign_in_email(
    body: {
      email: "john@example.com",
      password: "wrong-password"
    }
  )
rescue BetterAuth::APIError => error
  Rails.logger.info(
    status: error.status_code,
    code: error.code,
    message: error.message
  )
end

For Rack calls, Better Auth serializes errors as JSON responses.

status, headers, body = auth.call(env)

if status == 401
  payload = JSON.parse(body.join)
  warn payload.fetch("message", payload["error"])
end

Rate limit responses use status 429 and include x-retry-after.

status, headers, = auth.call(env)

if status == 429
  retry_after = headers["x-retry-after"].to_i
end

On this page