Skip to content

Commit 2c93faf

Browse files
committed
feat(organizations): add billing sub-resource
1 parent fdef412 commit 2c93faf

9 files changed

Lines changed: 360 additions & 2 deletions

File tree

src/resources/organizations/api.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,15 @@ Types:
3535
Methods:
3636

3737
- <code title="get /organizations/{organization_id}/logs/audit">client.organizations.logs.audit.<a href="./src/resources/organizations/logs/audit.ts">list</a>(organizationId, { ...params }) -> AuditListResponsesCursorPaginationAfter</code>
38+
39+
## Billing
40+
41+
### Usage
42+
43+
Types:
44+
45+
- <code><a href="./src/resources/organizations/billing/usage.ts">UsageGetResponse</a></code>
46+
47+
Methods:
48+
49+
- <code title="get /organizations/{organization_id}/billable/usage">client.organizations.billing.usage.<a href="./src/resources/organizations/billing/usage.ts">get</a>(organizationId, { ...params }) -> UsageGetResponse</code>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2+
3+
export * from './billing/index';
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2+
3+
import { APIResource } from '../../../resource';
4+
import * as UsageAPI from './usage';
5+
import { Usage, UsageGetParams, UsageGetResponse } from './usage';
6+
7+
export class Billing extends APIResource {
8+
usage: UsageAPI.Usage = new UsageAPI.Usage(this._client);
9+
}
10+
11+
Billing.Usage = Usage;
12+
13+
export declare namespace Billing {
14+
export { Usage as Usage, type UsageGetResponse as UsageGetResponse, type UsageGetParams as UsageGetParams };
15+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2+
3+
export { Billing } from './billing';
4+
export { Usage, type UsageGetResponse, type UsageGetParams } from './usage';
Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2+
3+
import { APIResource } from '../../../resource';
4+
import { isRequestOptions } from '../../../core';
5+
import * as Core from '../../../core';
6+
7+
export class Usage extends APIResource {
8+
/**
9+
* Returns cost and usage data for all accounts within an organization, aligned
10+
* with the [FinOps FOCUS v1.3](https://focus.finops.org/focus-specification/v1-3/)
11+
* Cost and Usage dataset specification.
12+
*
13+
* Each record represents one billable metric for one account on one day. This
14+
* includes all metered usage, including usage that falls within free-tier
15+
* allowances and may result in zero cost. The response includes usage for every
16+
* account belonging to the specified organization.
17+
*
18+
* **Note:** Cost and pricing fields are not yet populated and will be absent from
19+
* responses until billing integration is complete.
20+
*
21+
* When `from` and `to` are omitted, defaults to the start of the current month
22+
* through today. The maximum date range is 31 days.
23+
*/
24+
get(
25+
organizationId: string,
26+
query?: UsageGetParams,
27+
options?: Core.RequestOptions,
28+
): Core.APIPromise<UsageGetResponse>;
29+
get(organizationId: string, options?: Core.RequestOptions): Core.APIPromise<UsageGetResponse>;
30+
get(
31+
organizationId: string,
32+
query: UsageGetParams | Core.RequestOptions = {},
33+
options?: Core.RequestOptions,
34+
): Core.APIPromise<UsageGetResponse> {
35+
if (isRequestOptions(query)) {
36+
return this.get(organizationId, {}, query);
37+
}
38+
return (
39+
this._client.get(`/organizations/${organizationId}/billable/usage`, {
40+
query,
41+
...options,
42+
}) as Core.APIPromise<{ result: UsageGetResponse }>
43+
)._thenUnwrap((obj) => obj.result);
44+
}
45+
}
46+
47+
/**
48+
* Contains the array of cost and usage records.
49+
*/
50+
export type UsageGetResponse = Array<UsageGetResponse.UsageGetResponseItem>;
51+
52+
export namespace UsageGetResponse {
53+
/**
54+
* A single cost and usage record for a metered product within a specific charge
55+
* period, aligned with the FinOps FOCUS v1.3 specification.
56+
*/
57+
export interface UsageGetResponseItem {
58+
/**
59+
* Public identifier of the Cloudflare account (account tag).
60+
*/
61+
BillingAccountId: string;
62+
63+
/**
64+
* Display name of the Cloudflare account.
65+
*/
66+
BillingAccountName: string;
67+
68+
/**
69+
* Highest-level classification of a charge based on the nature of how it gets
70+
* billed. Currently only "Usage" is supported.
71+
*/
72+
ChargeCategory: 'Usage';
73+
74+
/**
75+
* Self-contained summary of the charge's purpose and price.
76+
*/
77+
ChargeDescription: string;
78+
79+
/**
80+
* Indicates how often a charge occurs. Currently only "Usage-Based" is supported.
81+
*/
82+
ChargeFrequency: 'Usage-Based';
83+
84+
/**
85+
* Exclusive end of the time interval during which the usage was consumed.
86+
*/
87+
ChargePeriodEnd: string;
88+
89+
/**
90+
* Inclusive start of the time interval during which the usage was consumed.
91+
*/
92+
ChargePeriodStart: string;
93+
94+
/**
95+
* Measured usage amount within the charge period. Reflects raw metered consumption
96+
* before pricing transformations.
97+
*/
98+
ConsumedQuantity: number;
99+
100+
/**
101+
* Unit of measure for the consumed quantity (e.g., "GB", "Requests",
102+
* "vCPU-Hours").
103+
*/
104+
ConsumedUnit: string;
105+
106+
/**
107+
* Name of the entity providing the underlying infrastructure or platform.
108+
*/
109+
HostProviderName: string;
110+
111+
/**
112+
* Name of the entity responsible for invoicing for the services consumed.
113+
*/
114+
InvoiceIssuerName: string;
115+
116+
/**
117+
* Name of the entity that made the services available for purchase.
118+
*/
119+
ServiceProviderName: string;
120+
121+
/**
122+
* The display name of the billable metric. Cloudflare extension; replaces FOCUS
123+
* SkuMeter.
124+
*/
125+
x_BillableMetricName: string;
126+
127+
/**
128+
* A charge serving as the basis for invoicing, inclusive of all reduced rates and
129+
* discounts while excluding the amortization of upfront charges (one-time or
130+
* recurring).
131+
*/
132+
BilledCost?: number | null;
133+
134+
/**
135+
* Currency that a charge was billed in (ISO 4217).
136+
*/
137+
BillingCurrency?: string | null;
138+
139+
/**
140+
* Exclusive end of the billing cycle that contains this usage record.
141+
*/
142+
BillingPeriodEnd?: string | null;
143+
144+
/**
145+
* Inclusive start of the billing cycle that contains this usage record.
146+
*/
147+
BillingPeriodStart?: string | null;
148+
149+
/**
150+
* Indicates whether the row represents a correction to one or more charges
151+
* invoiced in a previous billing period.
152+
*/
153+
ChargeClass?: 'Correction' | null;
154+
155+
/**
156+
* Cost calculated by multiplying ContractedUnitPrice and the corresponding
157+
* PricingQuantity.
158+
*/
159+
ContractedCost?: number | null;
160+
161+
/**
162+
* The agreed-upon unit price for a single PricingUnit of the associated billable
163+
* metric, inclusive of negotiated discounts, if present, while excluding any other
164+
* discounts.
165+
*/
166+
ContractedUnitPrice?: number | null;
167+
168+
/**
169+
* The amortized cost of the charge after applying all reduced rates, discounts,
170+
* and the applicable portion of relevant, prepaid purchases (one-time or
171+
* recurring) that covered the charge.
172+
*/
173+
EffectiveCost?: number | null;
174+
175+
/**
176+
* Cost calculated by multiplying ListUnitPrice and the corresponding
177+
* PricingQuantity.
178+
*/
179+
ListCost?: number | null;
180+
181+
/**
182+
* Suggested provider-published unit price for a single PricingUnit of the
183+
* associated billable metric, exclusive of any discounts.
184+
*/
185+
ListUnitPrice?: number | null;
186+
187+
/**
188+
* Volume of a given service used or purchased, based on the PricingUnit.
189+
*/
190+
PricingQuantity?: number | null;
191+
192+
/**
193+
* Provider-specified measurement unit for determining unit prices, indicating how
194+
* the provider rates measured usage after applying pricing rules like block
195+
* pricing.
196+
*/
197+
PricingUnit?: string | null;
198+
199+
/**
200+
* Provider-assigned identifier for an isolated geographic area where a service is
201+
* provided.
202+
*/
203+
RegionId?: string | null;
204+
205+
/**
206+
* Name of an isolated geographic area where a service is provided.
207+
*/
208+
RegionName?: string | null;
209+
210+
/**
211+
* Unique identifier assigned to a grouping of services. For Cloudflare, this is
212+
* the subscription or contract ID.
213+
*/
214+
SubAccountId?: string;
215+
216+
/**
217+
* Name assigned to a grouping of services. For Cloudflare, this is the
218+
* subscription or contract display name.
219+
*/
220+
SubAccountName?: string;
221+
222+
/**
223+
* The unique identifier for the billable metric in the Cloudflare catalog.
224+
* Cloudflare extension; replaces FOCUS SkuId.
225+
*/
226+
x_BillableMetricId?: string;
227+
228+
/**
229+
* The product family the charge belongs to (e.g., "R2", "Workers"). Cloudflare
230+
* extension; replaces FOCUS ServiceName.
231+
*/
232+
x_ProductFamilyName?: string;
233+
234+
/**
235+
* The identifier for the Cloudflare zone (zone tag). Cloudflare extension.
236+
*/
237+
x_ZoneId?: string | null;
238+
239+
/**
240+
* The display name of the Cloudflare zone. Cloudflare extension.
241+
*/
242+
x_ZoneName?: string | null;
243+
}
244+
}
245+
246+
export interface UsageGetParams {
247+
/**
248+
* Start date for the usage query (ISO 8601). Required if `to` is set. When omitted
249+
* along with `to`, defaults to the start of the current month. Filters by charge
250+
* period (when consumption happened), not billing period. The maximum date range
251+
* is 31 days.
252+
*/
253+
from?: string;
254+
255+
/**
256+
* Filter results by billable metric id (e.g., workers_standard_requests).
257+
*/
258+
metric?: string;
259+
260+
/**
261+
* End date for the usage query (ISO 8601). Required if `from` is set. When omitted
262+
* along with `from`, defaults to today. Filters by charge period (when consumption
263+
* happened), not billing period. The maximum date range is 31 days.
264+
*/
265+
to?: string;
266+
}
267+
268+
export declare namespace Usage {
269+
export { type UsageGetResponse as UsageGetResponse, type UsageGetParams as UsageGetParams };
270+
}

src/resources/organizations/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
22

3+
export { Billing } from './billing/index';
34
export { Logs } from './logs/index';
45
export {
56
OrganizationProfileResource,

src/resources/organizations/organizations.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import {
99
OrganizationProfileResource,
1010
OrganizationProfileUpdateParams,
1111
} from './organization-profile';
12+
import * as BillingAPI from './billing/billing';
13+
import { Billing } from './billing/billing';
1214
import * as LogsAPI from './logs/logs';
1315
import { Logs } from './logs/logs';
1416
import { SinglePage } from '../../pagination';
@@ -17,6 +19,7 @@ export class Organizations extends APIResource {
1719
organizationProfile: OrganizationProfileAPI.OrganizationProfileResource =
1820
new OrganizationProfileAPI.OrganizationProfileResource(this._client);
1921
logs: LogsAPI.Logs = new LogsAPI.Logs(this._client);
22+
billing: BillingAPI.Billing = new BillingAPI.Billing(this._client);
2023

2124
/**
2225
* Create a new organization for a user. (Currently in Public Beta - see
@@ -300,6 +303,7 @@ export namespace OrganizationListParams {
300303
Organizations.OrganizationsSinglePage = OrganizationsSinglePage;
301304
Organizations.OrganizationProfileResource = OrganizationProfileResource;
302305
Organizations.Logs = Logs;
306+
Organizations.Billing = Billing;
303307

304308
export declare namespace Organizations {
305309
export {
@@ -318,4 +322,6 @@ export declare namespace Organizations {
318322
};
319323

320324
export { Logs as Logs };
325+
326+
export { Billing as Billing };
321327
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2+
3+
import Cloudflare from 'cloudflare';
4+
import { Response } from 'node-fetch';
5+
6+
const client = new Cloudflare({
7+
apiKey: '144c9defac04969c7bfad8efaa8ea194',
8+
apiEmail: 'user@example.com',
9+
baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010',
10+
});
11+
12+
describe('resource usage', () => {
13+
test('get', async () => {
14+
const responsePromise = client.organizations.billing.usage.get('023e105f4ecef8ad9ca31a8372d0c353');
15+
const rawResponse = await responsePromise.asResponse();
16+
expect(rawResponse).toBeInstanceOf(Response);
17+
const response = await responsePromise;
18+
expect(response).not.toBeInstanceOf(Response);
19+
const dataAndResponse = await responsePromise.withResponse();
20+
expect(dataAndResponse.data).toBe(response);
21+
expect(dataAndResponse.response).toBe(rawResponse);
22+
});
23+
24+
test('get: request options instead of params are passed correctly', async () => {
25+
// ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error
26+
await expect(
27+
client.organizations.billing.usage.get('023e105f4ecef8ad9ca31a8372d0c353', {
28+
path: '/_stainless_unknown_path',
29+
}),
30+
).rejects.toThrow(Cloudflare.NotFoundError);
31+
});
32+
33+
test('get: request options and params are passed correctly', async () => {
34+
// ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error
35+
await expect(
36+
client.organizations.billing.usage.get(
37+
'023e105f4ecef8ad9ca31a8372d0c353',
38+
{
39+
from: '2025-05-01',
40+
metric: 'workers_standard_requests',
41+
to: '2025-05-31',
42+
},
43+
{ path: '/_stainless_unknown_path' },
44+
),
45+
).rejects.toThrow(Cloudflare.NotFoundError);
46+
});
47+
});

0 commit comments

Comments
 (0)