Class: AiUsage::AnthropicAdminClient

Inherits:
Object
  • Object
show all
Defined in:
app/services/ai_usage/anthropic_admin_client.rb

Overview

Read-only client for Anthropic's Usage & Cost Admin API (organization-level).

Requires an Admin API key (sk-ant-admin01-...), which is distinct from
the app's per-environment claude_ai.api_key. The key is resolved from
credentials (claude_ai.admin_api_key, the reporting service-account key)
with an ANTHROPIC_ADMIN_KEY env fallback for local/dev runs.

Cost amounts come back as decimal strings in cents; this client converts
to USD. See https://platform.claude.com/docs/en/api/usage-cost-api

Defined Under Namespace

Classes: Error, MissingKeyError

Constant Summary collapse

BASE_URL =

API host.

'https://api.anthropic.com'
API_VERSION =

Pinned API version header.

'2023-06-01'

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(api_key: self.class.default_api_key) ⇒ AnthropicAdminClient

Returns a new instance of AnthropicAdminClient.

Parameters:

  • api_key (String, nil) (defaults to: self.class.default_api_key)

    admin key; defaults to the configured one

Raises:



25
26
27
28
# File 'app/services/ai_usage/anthropic_admin_client.rb', line 25

def initialize(api_key: self.class.default_api_key)
  @api_key = api_key.presence
  raise MissingKeyError, 'No Anthropic admin key (set claude_ai.admin_api_key or ANTHROPIC_ADMIN_KEY)' unless @api_key
end

Class Method Details

.default_api_keyString?

Returns the admin key from credentials, else the env var.

Returns:

  • (String, nil)

    the admin key from credentials, else the env var



31
32
33
34
35
# File 'app/services/ai_usage/anthropic_admin_client.rb', line 31

def self.default_api_key
  Heatwave::Configuration.fetch(:claude_ai, :admin_api_key).presence || ENV['ANTHROPIC_ADMIN_KEY'].presence
rescue StandardError
  ENV['ANTHROPIC_ADMIN_KEY'].presence
end

Instance Method Details

#cost_by_model(starting_at:, ending_at:) ⇒ Hash

Real organization cost in USD for the window, grouped by model.

Parameters:

  • starting_at (String)

    ISO-8601 inclusive start (e.g. "2026-05-22T00:00:00Z")

  • ending_at (String)

    ISO-8601 exclusive end

Returns:

  • (Hash)

    { total_usd: Float, by_model: { String => Float } }



42
43
44
45
46
47
48
49
50
# File 'app/services/ai_usage/anthropic_admin_client.rb', line 42

def cost_by_model(starting_at:, ending_at:)
  by_model = Hash.new(0.0)
  each_result('/v1/organizations/cost_report',
              'starting_at' => starting_at, 'ending_at' => ending_at, 'group_by[]' => 'description') do |row|
    model = row['model'].presence || row['description'].to_s
    by_model[model] += row['amount'].to_f / 100.0 # amounts are decimal-string cents
  end
  { total_usd: by_model.values.sum.round(2), by_model: by_model.transform_values { |v| v.round(2) } }
end