Skip to content

Commit eb96f3d

Browse files
feat: Add GetOrgAICreditUsage and GetUserAICreditUsage endpoints (#4282)
1 parent 531d1cf commit eb96f3d

2 files changed

Lines changed: 384 additions & 0 deletions

File tree

‎github/billing.go‎

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,3 +392,55 @@ func (s *BillingService) GetPremiumRequestUsageReport(ctx context.Context, user
392392

393393
return premiumRequestUsageReport, resp, nil
394394
}
395+
396+
// GetOrgAICreditUsage returns a report of the AI credit usage for an organization.
397+
//
398+
// GitHub API docs: https://docs.github.com/rest/billing/usage?apiVersion=2022-11-28#get-billing-ai-credit-usage-report-for-an-organization
399+
//
400+
//meta:operation GET /organizations/{org}/settings/billing/ai_credit/usage
401+
func (s *BillingService) GetOrgAICreditUsage(ctx context.Context, org string, opts *PremiumRequestUsageReportOptions) (*PremiumRequestUsageReport, *Response, error) {
402+
u := fmt.Sprintf("organizations/%v/settings/billing/ai_credit/usage", org)
403+
u, err := addOptions(u, opts)
404+
if err != nil {
405+
return nil, nil, err
406+
}
407+
408+
req, err := s.client.NewRequest(ctx, "GET", u, nil)
409+
if err != nil {
410+
return nil, nil, err
411+
}
412+
413+
var aiCreditUsage *PremiumRequestUsageReport
414+
resp, err := s.client.Do(req, &aiCreditUsage)
415+
if err != nil {
416+
return nil, resp, err
417+
}
418+
419+
return aiCreditUsage, resp, nil
420+
}
421+
422+
// GetUserAICreditUsage returns a report of the AI credit usage for a user.
423+
//
424+
// GitHub API docs: https://docs.github.com/rest/billing/usage?apiVersion=2022-11-28#get-billing-ai-credit-usage-report-for-a-user
425+
//
426+
//meta:operation GET /users/{username}/settings/billing/ai_credit/usage
427+
func (s *BillingService) GetUserAICreditUsage(ctx context.Context, username string, opts *PremiumRequestUsageReportOptions) (*PremiumRequestUsageReport, *Response, error) {
428+
u := fmt.Sprintf("users/%v/settings/billing/ai_credit/usage", username)
429+
u, err := addOptions(u, opts)
430+
if err != nil {
431+
return nil, nil, err
432+
}
433+
434+
req, err := s.client.NewRequest(ctx, "GET", u, nil)
435+
if err != nil {
436+
return nil, nil, err
437+
}
438+
439+
var aiCreditUsage *PremiumRequestUsageReport
440+
resp, err := s.client.Do(req, &aiCreditUsage)
441+
if err != nil {
442+
return nil, resp, err
443+
}
444+
445+
return aiCreditUsage, resp, nil
446+
}

‎github/billing_test.go‎

Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -720,3 +720,335 @@ func TestBillingService_PremiumRequestUsageItem_FloatQuantities(t *testing.T) {
720720
t.Errorf("Billing.GetOrganizationPremiumRequestUsageReport returned %+v, want %+v", report, want)
721721
}
722722
}
723+
724+
func TestBillingService_GetOrgAICreditUsage(t *testing.T) {
725+
t.Parallel()
726+
client, mux, _ := setup(t)
727+
728+
mux.HandleFunc("/organizations/o/settings/billing/ai_credit/usage", func(w http.ResponseWriter, r *http.Request) {
729+
testMethod(t, r, "GET")
730+
testFormValues(t, r, values{
731+
"year": "2025",
732+
"month": "6",
733+
"user": "testuser",
734+
})
735+
fmt.Fprint(w, `{
736+
"timePeriod": {
737+
"year": 2025,
738+
"month": 6
739+
},
740+
"organization": "GitHub",
741+
"user": "testuser",
742+
"product": "Copilot",
743+
"model": "GPT-5",
744+
"usageItems": [
745+
{
746+
"product": "Copilot",
747+
"sku": "Copilot AI Credits",
748+
"model": "GPT-5",
749+
"unitType": "credits",
750+
"pricePerUnit": 0.01,
751+
"grossQuantity": 100,
752+
"grossAmount": 1.0,
753+
"discountQuantity": 0,
754+
"discountAmount": 0.0,
755+
"netQuantity": 100,
756+
"netAmount": 1.0
757+
}
758+
]
759+
}`)
760+
})
761+
762+
ctx := t.Context()
763+
opts := &PremiumRequestUsageReportOptions{
764+
Year: Ptr(2025),
765+
Month: Ptr(6),
766+
User: Ptr("testuser"),
767+
}
768+
report, _, err := client.Billing.GetOrgAICreditUsage(ctx, "o", opts)
769+
if err != nil {
770+
t.Errorf("Billing.GetOrgAICreditUsage returned error: %v", err)
771+
}
772+
773+
want := &PremiumRequestUsageReport{
774+
TimePeriod: PremiumRequestUsageTimePeriod{
775+
Year: 2025,
776+
Month: Ptr(6),
777+
},
778+
Organization: Ptr("GitHub"),
779+
User: Ptr("testuser"),
780+
Product: Ptr("Copilot"),
781+
Model: Ptr("GPT-5"),
782+
UsageItems: []*PremiumRequestUsageItem{
783+
{
784+
Product: "Copilot",
785+
SKU: "Copilot AI Credits",
786+
Model: "GPT-5",
787+
UnitType: "credits",
788+
PricePerUnit: 0.01,
789+
GrossQuantity: 100.0,
790+
GrossAmount: 1.0,
791+
DiscountQuantity: 0.0,
792+
DiscountAmount: 0.0,
793+
NetQuantity: 100.0,
794+
NetAmount: 1.0,
795+
},
796+
},
797+
}
798+
if !cmp.Equal(report, want) {
799+
t.Errorf("Billing.GetOrgAICreditUsage returned %+v, want %+v", report, want)
800+
}
801+
802+
const methodName = "GetOrgAICreditUsage"
803+
testBadOptions(t, methodName, func() (err error) {
804+
_, _, err = client.Billing.GetOrgAICreditUsage(ctx, "\n", opts)
805+
return err
806+
})
807+
808+
testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
809+
got, resp, err := client.Billing.GetOrgAICreditUsage(ctx, "o", nil)
810+
if got != nil {
811+
t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
812+
}
813+
return resp, err
814+
})
815+
}
816+
817+
func TestBillingService_GetOrgAICreditUsage_invalidOrg(t *testing.T) {
818+
t.Parallel()
819+
client, _, _ := setup(t)
820+
821+
ctx := t.Context()
822+
_, _, err := client.Billing.GetOrgAICreditUsage(ctx, "%", nil)
823+
testURLParseError(t, err)
824+
}
825+
826+
func TestBillingService_GetOrgAICreditUsage_FloatQuantities(t *testing.T) {
827+
t.Parallel()
828+
client, mux, _ := setup(t)
829+
830+
mux.HandleFunc("/organizations/o/settings/billing/ai_credit/usage", func(w http.ResponseWriter, r *http.Request) {
831+
testMethod(t, r, "GET")
832+
fmt.Fprint(w, `{
833+
"timePeriod": {
834+
"year": 2025,
835+
"month": 6,
836+
"day": 15
837+
},
838+
"organization": "testorg",
839+
"usageItems": [
840+
{
841+
"product": "Copilot",
842+
"sku": "Copilot AI Credits",
843+
"model": "GPT-5",
844+
"unitType": "credits",
845+
"pricePerUnit": 0.01,
846+
"grossQuantity": 1500.5,
847+
"grossAmount": 15.005,
848+
"discountQuantity": 100.5,
849+
"discountAmount": 1.005,
850+
"netQuantity": 1400.0,
851+
"netAmount": 14.0
852+
}
853+
]
854+
}`)
855+
})
856+
857+
ctx := t.Context()
858+
report, _, err := client.Billing.GetOrgAICreditUsage(ctx, "o", nil)
859+
if err != nil {
860+
t.Fatalf("Billing.GetOrgAICreditUsage returned error: %v", err)
861+
}
862+
863+
want := &PremiumRequestUsageReport{
864+
TimePeriod: PremiumRequestUsageTimePeriod{
865+
Year: 2025,
866+
Month: Ptr(6),
867+
Day: Ptr(15),
868+
},
869+
Organization: Ptr("testorg"),
870+
UsageItems: []*PremiumRequestUsageItem{
871+
{
872+
Product: "Copilot",
873+
SKU: "Copilot AI Credits",
874+
Model: "GPT-5",
875+
UnitType: "credits",
876+
PricePerUnit: 0.01,
877+
GrossQuantity: 1500.5,
878+
GrossAmount: 15.005,
879+
DiscountQuantity: 100.5,
880+
DiscountAmount: 1.005,
881+
NetQuantity: 1400.0,
882+
NetAmount: 14.0,
883+
},
884+
},
885+
}
886+
if !cmp.Equal(report, want) {
887+
t.Errorf("Billing.GetOrgAICreditUsage returned %+v, want %+v", report, want)
888+
}
889+
}
890+
891+
func TestBillingService_GetUserAICreditUsage(t *testing.T) {
892+
t.Parallel()
893+
client, mux, _ := setup(t)
894+
895+
mux.HandleFunc("/users/u/settings/billing/ai_credit/usage", func(w http.ResponseWriter, r *http.Request) {
896+
testMethod(t, r, "GET")
897+
testFormValues(t, r, values{
898+
"year": "2025",
899+
"month": "6",
900+
"user": "testuser",
901+
})
902+
fmt.Fprint(w, `{
903+
"timePeriod": {
904+
"year": 2025,
905+
"month": 6
906+
},
907+
"user": "testuser",
908+
"product": "Copilot",
909+
"model": "GPT-5",
910+
"usageItems": [
911+
{
912+
"product": "Copilot",
913+
"sku": "Copilot AI Credits",
914+
"model": "GPT-5",
915+
"unitType": "credits",
916+
"pricePerUnit": 0.01,
917+
"grossQuantity": 100,
918+
"grossAmount": 1.0,
919+
"discountQuantity": 0,
920+
"discountAmount": 0.0,
921+
"netQuantity": 100,
922+
"netAmount": 1.0
923+
}
924+
]
925+
}`)
926+
})
927+
928+
ctx := t.Context()
929+
opts := &PremiumRequestUsageReportOptions{
930+
Year: Ptr(2025),
931+
Month: Ptr(6),
932+
User: Ptr("testuser"),
933+
}
934+
report, _, err := client.Billing.GetUserAICreditUsage(ctx, "u", opts)
935+
if err != nil {
936+
t.Errorf("Billing.GetUserAICreditUsage returned error: %v", err)
937+
}
938+
939+
want := &PremiumRequestUsageReport{
940+
TimePeriod: PremiumRequestUsageTimePeriod{
941+
Year: 2025,
942+
Month: Ptr(6),
943+
},
944+
User: Ptr("testuser"),
945+
Product: Ptr("Copilot"),
946+
Model: Ptr("GPT-5"),
947+
UsageItems: []*PremiumRequestUsageItem{
948+
{
949+
Product: "Copilot",
950+
SKU: "Copilot AI Credits",
951+
Model: "GPT-5",
952+
UnitType: "credits",
953+
PricePerUnit: 0.01,
954+
GrossQuantity: 100.0,
955+
GrossAmount: 1.0,
956+
DiscountQuantity: 0.0,
957+
DiscountAmount: 0.0,
958+
NetQuantity: 100.0,
959+
NetAmount: 1.0,
960+
},
961+
},
962+
}
963+
if !cmp.Equal(report, want) {
964+
t.Errorf("Billing.GetUserAICreditUsage returned %+v, want %+v", report, want)
965+
}
966+
967+
const methodName = "GetUserAICreditUsage"
968+
testBadOptions(t, methodName, func() (err error) {
969+
_, _, err = client.Billing.GetUserAICreditUsage(ctx, "\n", opts)
970+
return err
971+
})
972+
973+
testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
974+
got, resp, err := client.Billing.GetUserAICreditUsage(ctx, "u", nil)
975+
if got != nil {
976+
t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
977+
}
978+
return resp, err
979+
})
980+
}
981+
982+
func TestBillingService_GetUserAICreditUsage_invalidUser(t *testing.T) {
983+
t.Parallel()
984+
client, _, _ := setup(t)
985+
986+
ctx := t.Context()
987+
_, _, err := client.Billing.GetUserAICreditUsage(ctx, "%", nil)
988+
testURLParseError(t, err)
989+
}
990+
991+
func TestBillingService_GetUserAICreditUsage_FloatQuantities(t *testing.T) {
992+
t.Parallel()
993+
client, mux, _ := setup(t)
994+
995+
mux.HandleFunc("/users/u/settings/billing/ai_credit/usage", func(w http.ResponseWriter, r *http.Request) {
996+
testMethod(t, r, "GET")
997+
fmt.Fprint(w, `{
998+
"timePeriod": {
999+
"year": 2025,
1000+
"month": 6,
1001+
"day": 15
1002+
},
1003+
"user": "testuser",
1004+
"usageItems": [
1005+
{
1006+
"product": "Copilot",
1007+
"sku": "Copilot AI Credits",
1008+
"model": "GPT-5",
1009+
"unitType": "credits",
1010+
"pricePerUnit": 0.01,
1011+
"grossQuantity": 1500.5,
1012+
"grossAmount": 15.005,
1013+
"discountQuantity": 100.5,
1014+
"discountAmount": 1.005,
1015+
"netQuantity": 1400.0,
1016+
"netAmount": 14.0
1017+
}
1018+
]
1019+
}`)
1020+
})
1021+
1022+
ctx := t.Context()
1023+
report, _, err := client.Billing.GetUserAICreditUsage(ctx, "u", nil)
1024+
if err != nil {
1025+
t.Fatalf("Billing.GetUserAICreditUsage returned error: %v", err)
1026+
}
1027+
1028+
want := &PremiumRequestUsageReport{
1029+
TimePeriod: PremiumRequestUsageTimePeriod{
1030+
Year: 2025,
1031+
Month: Ptr(6),
1032+
Day: Ptr(15),
1033+
},
1034+
User: Ptr("testuser"),
1035+
UsageItems: []*PremiumRequestUsageItem{
1036+
{
1037+
Product: "Copilot",
1038+
SKU: "Copilot AI Credits",
1039+
Model: "GPT-5",
1040+
UnitType: "credits",
1041+
PricePerUnit: 0.01,
1042+
GrossQuantity: 1500.5,
1043+
GrossAmount: 15.005,
1044+
DiscountQuantity: 100.5,
1045+
DiscountAmount: 1.005,
1046+
NetQuantity: 1400.0,
1047+
NetAmount: 14.0,
1048+
},
1049+
},
1050+
}
1051+
if !cmp.Equal(report, want) {
1052+
t.Errorf("Billing.GetUserAICreditUsage returned %+v, want %+v", report, want)
1053+
}
1054+
}

0 commit comments

Comments
 (0)