Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions github/billing.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,35 @@ type PremiumRequestUsageReportOptions struct {
Product *string `url:"product,omitempty"`
}

// AICreditUsageReportOptions specifies optional parameters
// for the enhanced billing platform AI credit usage report.
type AICreditUsageReportOptions struct {
// If specified, only return results for a single year.
// The value of year is an integer with four digits representing a year. For example, 2025.
// Default value is the current year.
Year int `url:"year,omitempty"`

// If specified, only return results for a single month.
// The value of month is an integer between 1 and 12. Default value is the current month.
// If no year is specified the default year is used.
Month int `url:"month,omitempty"`

// If specified, only return results for a single day.
// The value of day is an integer between 1 and 31.
// If no year or month is specified, the default year and month are used.
Day int `url:"day,omitempty"`

// The user name to query usage for. The name is not case-sensitive.
// This parameter is only supported for organization-level reports.
User string `url:"user,omitempty"`

// The model name to query usage for. The name is not case-sensitive.
Model string `url:"model,omitempty"`

// The product name to query usage for. The name is not case-sensitive.
Product string `url:"product,omitempty"`
}

// UsageItem represents a single usage item in the enhanced billing platform report.
type UsageItem struct {
Date string `json:"date"`
Expand Down Expand Up @@ -171,6 +200,40 @@ type PremiumRequestUsageReport struct {
UsageItems []*PremiumRequestUsageItem `json:"usageItems"`
}

// AICreditUsageItem represents a single usage line item in AI credit usage reports.
type AICreditUsageItem struct {
Product string `json:"product"`
SKU string `json:"sku"`
Model string `json:"model"`
UnitType string `json:"unitType"`
PricePerUnit float64 `json:"pricePerUnit"`
GrossQuantity float64 `json:"grossQuantity"`
GrossAmount float64 `json:"grossAmount"`
DiscountQuantity float64 `json:"discountQuantity"`
DiscountAmount float64 `json:"discountAmount"`
NetQuantity float64 `json:"netQuantity"`
NetAmount float64 `json:"netAmount"`
}

// AICreditUsageTimePeriod represents a time period for AI credit usage reports.
type AICreditUsageTimePeriod struct {
Year int `json:"year"`
Month *int `json:"month,omitempty"`
Day *int `json:"day,omitempty"`
}

// AICreditUsageReport represents the AI credit usage report response.
type AICreditUsageReport struct {
TimePeriod AICreditUsageTimePeriod `json:"timePeriod"`
// Organization is only set for organization-level reports.
Organization *string `json:"organization,omitempty"`
// User is only set for user-level reports.
User *string `json:"user,omitempty"`
Product *string `json:"product,omitempty"`
Model *string `json:"model,omitempty"`
UsageItems []*AICreditUsageItem `json:"usageItems"`
}

// GetOrganizationPackagesBilling returns the free and paid storage used for GitHub Packages in gigabytes for an Org.
//
// This endpoint appears to have disappeared from the official GitHub v3 API documentation website.
Expand Down Expand Up @@ -392,3 +455,61 @@ func (s *BillingService) GetPremiumRequestUsageReport(ctx context.Context, user

return premiumRequestUsageReport, resp, nil
}

// GetOrganizationAICreditUsageReport returns a report of the AI credit
// usage for an organization using the enhanced billing platform.
//
// Note: Only data from the past 24 months is accessible via this endpoint.
//
// GitHub API docs: https://docs.github.com/rest/billing/usage?apiVersion=2022-11-28#get-billing-ai-credit-usage-report-for-an-organization
//
//meta:operation GET /organizations/{org}/settings/billing/ai_credit/usage
func (s *BillingService) GetOrganizationAICreditUsageReport(ctx context.Context, org string, opts *AICreditUsageReportOptions) (*AICreditUsageReport, *Response, error) {
u := fmt.Sprintf("organizations/%v/settings/billing/ai_credit/usage", org)
u, err := addOptions(u, opts)
if err != nil {
return nil, nil, err
}

req, err := s.client.NewRequest(ctx, "GET", u, nil)
if err != nil {
return nil, nil, err
}

var aiCreditUsageReport *AICreditUsageReport
resp, err := s.client.Do(req, &aiCreditUsageReport)
if err != nil {
return nil, resp, err
}

return aiCreditUsageReport, resp, nil
}

// GetAICreditUsageReport returns a report of the AI credit
// usage for a user using the enhanced billing platform.
//
// Note: Only data from the past 24 months is accessible via this endpoint.
//
// GitHub API docs: https://docs.github.com/rest/billing/usage?apiVersion=2022-11-28#get-billing-ai-credit-usage-report-for-a-user
//
//meta:operation GET /users/{username}/settings/billing/ai_credit/usage
func (s *BillingService) GetAICreditUsageReport(ctx context.Context, user string, opts *AICreditUsageReportOptions) (*AICreditUsageReport, *Response, error) {
u := fmt.Sprintf("users/%v/settings/billing/ai_credit/usage", user)
u, err := addOptions(u, opts)
if err != nil {
return nil, nil, err
}

req, err := s.client.NewRequest(ctx, "GET", u, nil)
if err != nil {
return nil, nil, err
}

var aiCreditUsageReport *AICreditUsageReport
resp, err := s.client.Do(req, &aiCreditUsageReport)
if err != nil {
return nil, resp, err
}

return aiCreditUsageReport, resp, nil
}
196 changes: 196 additions & 0 deletions github/billing_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,202 @@ func TestBillingService_GetPremiumRequestUsageReport_invalidUser(t *testing.T) {
testURLParseError(t, err)
}

func TestBillingService_GetOrganizationAICreditUsageReport(t *testing.T) {
t.Parallel()
client, mux, _ := setup(t)
mux.HandleFunc("/organizations/o/settings/billing/ai_credit/usage", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
testFormValues(t, r, values{
"year": "2026",
"month": "6",
"user": "testuser",
"model": "Auto: Claude Sonnet 4.6",
})
fmt.Fprint(w, `{
"timePeriod": {
"year": 2026,
"month": 6
},
"organization": "GitHub",
"user": "testuser",
"product": "Copilot",
"model": "Auto: Claude Sonnet 4.6",
"usageItems": [
{
"product": "Copilot",
"sku": "Copilot AI Credits",
"model": "Auto: Claude Sonnet 4.6",
"unitType": "ai-credits",
"pricePerUnit": 0.01,
"grossQuantity": 9498.6969165,
"grossAmount": 94.986969165,
"discountQuantity": 9564.710256,
"discountAmount": 95.64710256,
"netQuantity": 0.0,
"netAmount": 0.0
}
]
}`)
})
ctx := t.Context()
opts := &AICreditUsageReportOptions{
Year: 2026,
Month: 6,
User: "testuser",
Model: "Auto: Claude Sonnet 4.6",
}
report, _, err := client.Billing.GetOrganizationAICreditUsageReport(ctx, "o", opts)
if err != nil {
t.Errorf("Billing.GetOrganizationAICreditUsageReport returned error: %v", err)
}
want := &AICreditUsageReport{
TimePeriod: AICreditUsageTimePeriod{
Year: 2026,
Month: Ptr(6),
},
Organization: Ptr("GitHub"),
User: Ptr("testuser"),
Product: Ptr("Copilot"),
Model: Ptr("Auto: Claude Sonnet 4.6"),
UsageItems: []*AICreditUsageItem{
{
Product: "Copilot",
SKU: "Copilot AI Credits",
Model: "Auto: Claude Sonnet 4.6",
UnitType: "ai-credits",
PricePerUnit: 0.01,
GrossQuantity: 9498.6969165,
GrossAmount: 94.986969165,
DiscountQuantity: 9564.710256,
DiscountAmount: 95.64710256,
NetQuantity: 0.0,
NetAmount: 0.0,
},
},
}
if !cmp.Equal(report, want) {
t.Errorf("Billing.GetOrganizationAICreditUsageReport returned %+v, want %+v", report, want)
}

const methodName = "GetOrganizationAICreditUsageReport"
testBadOptions(t, methodName, func() (err error) {
_, _, err = client.Billing.GetOrganizationAICreditUsageReport(ctx, "\n", opts)
return err
})

testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
got, resp, err := client.Billing.GetOrganizationAICreditUsageReport(ctx, "o", nil)
if got != nil {
t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
}
return resp, err
})
}

func TestBillingService_GetOrganizationAICreditUsageReport_invalidOrg(t *testing.T) {
t.Parallel()
client, _, _ := setup(t)

ctx := t.Context()
_, _, err := client.Billing.GetOrganizationAICreditUsageReport(ctx, "%", nil)
testURLParseError(t, err)
}

func TestBillingService_GetAICreditUsageReport(t *testing.T) {
t.Parallel()
client, mux, _ := setup(t)
mux.HandleFunc("/users/u/settings/billing/ai_credit/usage", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
testFormValues(t, r, values{
"year": "2026",
"day": "15",
"product": "Copilot AI Credits",
})
fmt.Fprint(w, `{
"timePeriod": {
"year": 2026,
"day": 15
},
"user": "monalisa",
"product": "Copilot AI Credits",
"usageItems": [
{
"product": "Copilot AI Credits",
"sku": "AI Credit",
"model": "GPT-5",
"unitType": "ai-credits",
"pricePerUnit": 0.01,
"grossQuantity": 100,
"grossAmount": 1.0,
"discountQuantity": 0,
"discountAmount": 0.0,
"netQuantity": 100,
"netAmount": 1.0
}
]
}`)
})
ctx := t.Context()
opts := &AICreditUsageReportOptions{
Year: 2026,
Day: 15,
Product: "Copilot AI Credits",
}
report, _, err := client.Billing.GetAICreditUsageReport(ctx, "u", opts)
if err != nil {
t.Errorf("Billing.GetAICreditUsageReport returned error: %v", err)
}
want := &AICreditUsageReport{
TimePeriod: AICreditUsageTimePeriod{
Year: 2026,
Day: Ptr(15),
},
User: Ptr("monalisa"),
Product: Ptr("Copilot AI Credits"),
UsageItems: []*AICreditUsageItem{
{
Product: "Copilot AI Credits",
SKU: "AI Credit",
Model: "GPT-5",
UnitType: "ai-credits",
PricePerUnit: 0.01,
GrossQuantity: 100.0,
GrossAmount: 1.0,
DiscountQuantity: 0.0,
DiscountAmount: 0.0,
NetQuantity: 100.0,
NetAmount: 1.0,
},
},
}
if !cmp.Equal(report, want) {
t.Errorf("Billing.GetAICreditUsageReport returned %+v, want %+v", report, want)
}

const methodName = "GetAICreditUsageReport"
testBadOptions(t, methodName, func() (err error) {
_, _, err = client.Billing.GetAICreditUsageReport(ctx, "\n", opts)
return err
})

testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
got, resp, err := client.Billing.GetAICreditUsageReport(ctx, "u", nil)
if got != nil {
t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
}
return resp, err
})
}

func TestBillingService_GetAICreditUsageReport_invalidUser(t *testing.T) {
t.Parallel()
client, _, _ := setup(t)

ctx := t.Context()
_, _, err := client.Billing.GetAICreditUsageReport(ctx, "%", nil)
testURLParseError(t, err)
}

func TestBillingService_PremiumRequestUsageItem_FloatQuantities(t *testing.T) {
t.Parallel()
client, mux, _ := setup(t)
Expand Down
Loading
Loading