diff --git a/.stats.yml b/.stats.yml
index 58526a7..85f79b6 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1 +1 @@
-configured_endpoints: 48
+configured_endpoints: 49
diff --git a/api.md b/api.md
index a331b68..177624f 100644
--- a/api.md
+++ b/api.md
@@ -206,6 +206,18 @@ Methods:
- client.accounts.usage.get(\*\*params) -> UsageGetResponse
+## UsageAnalytics
+
+Types:
+
+```python
+from imagekitio.types.accounts import RequestBandwidthEntry, UsageAnalyticsResponse
+```
+
+Methods:
+
+- client.accounts.usage_analytics.get(\*\*params) -> UsageAnalyticsResponse
+
## Origins
Types:
diff --git a/src/imagekitio/resources/accounts/__init__.py b/src/imagekitio/resources/accounts/__init__.py
index fc56413..1e10f73 100644
--- a/src/imagekitio/resources/accounts/__init__.py
+++ b/src/imagekitio/resources/accounts/__init__.py
@@ -32,6 +32,14 @@
URLEndpointsResourceWithStreamingResponse,
AsyncURLEndpointsResourceWithStreamingResponse,
)
+from .usage_analytics import (
+ UsageAnalyticsResource,
+ AsyncUsageAnalyticsResource,
+ UsageAnalyticsResourceWithRawResponse,
+ AsyncUsageAnalyticsResourceWithRawResponse,
+ UsageAnalyticsResourceWithStreamingResponse,
+ AsyncUsageAnalyticsResourceWithStreamingResponse,
+)
__all__ = [
"UsageResource",
@@ -40,6 +48,12 @@
"AsyncUsageResourceWithRawResponse",
"UsageResourceWithStreamingResponse",
"AsyncUsageResourceWithStreamingResponse",
+ "UsageAnalyticsResource",
+ "AsyncUsageAnalyticsResource",
+ "UsageAnalyticsResourceWithRawResponse",
+ "AsyncUsageAnalyticsResourceWithRawResponse",
+ "UsageAnalyticsResourceWithStreamingResponse",
+ "AsyncUsageAnalyticsResourceWithStreamingResponse",
"OriginsResource",
"AsyncOriginsResource",
"OriginsResourceWithRawResponse",
diff --git a/src/imagekitio/resources/accounts/accounts.py b/src/imagekitio/resources/accounts/accounts.py
index 461e8cf..a15e343 100644
--- a/src/imagekitio/resources/accounts/accounts.py
+++ b/src/imagekitio/resources/accounts/accounts.py
@@ -28,6 +28,14 @@
URLEndpointsResourceWithStreamingResponse,
AsyncURLEndpointsResourceWithStreamingResponse,
)
+from .usage_analytics import (
+ UsageAnalyticsResource,
+ AsyncUsageAnalyticsResource,
+ UsageAnalyticsResourceWithRawResponse,
+ AsyncUsageAnalyticsResourceWithRawResponse,
+ UsageAnalyticsResourceWithStreamingResponse,
+ AsyncUsageAnalyticsResourceWithStreamingResponse,
+)
__all__ = ["AccountsResource", "AsyncAccountsResource"]
@@ -37,6 +45,10 @@ class AccountsResource(SyncAPIResource):
def usage(self) -> UsageResource:
return UsageResource(self._client)
+ @cached_property
+ def usage_analytics(self) -> UsageAnalyticsResource:
+ return UsageAnalyticsResource(self._client)
+
@cached_property
def origins(self) -> OriginsResource:
return OriginsResource(self._client)
@@ -70,6 +82,10 @@ class AsyncAccountsResource(AsyncAPIResource):
def usage(self) -> AsyncUsageResource:
return AsyncUsageResource(self._client)
+ @cached_property
+ def usage_analytics(self) -> AsyncUsageAnalyticsResource:
+ return AsyncUsageAnalyticsResource(self._client)
+
@cached_property
def origins(self) -> AsyncOriginsResource:
return AsyncOriginsResource(self._client)
@@ -106,6 +122,10 @@ def __init__(self, accounts: AccountsResource) -> None:
def usage(self) -> UsageResourceWithRawResponse:
return UsageResourceWithRawResponse(self._accounts.usage)
+ @cached_property
+ def usage_analytics(self) -> UsageAnalyticsResourceWithRawResponse:
+ return UsageAnalyticsResourceWithRawResponse(self._accounts.usage_analytics)
+
@cached_property
def origins(self) -> OriginsResourceWithRawResponse:
return OriginsResourceWithRawResponse(self._accounts.origins)
@@ -123,6 +143,10 @@ def __init__(self, accounts: AsyncAccountsResource) -> None:
def usage(self) -> AsyncUsageResourceWithRawResponse:
return AsyncUsageResourceWithRawResponse(self._accounts.usage)
+ @cached_property
+ def usage_analytics(self) -> AsyncUsageAnalyticsResourceWithRawResponse:
+ return AsyncUsageAnalyticsResourceWithRawResponse(self._accounts.usage_analytics)
+
@cached_property
def origins(self) -> AsyncOriginsResourceWithRawResponse:
return AsyncOriginsResourceWithRawResponse(self._accounts.origins)
@@ -140,6 +164,10 @@ def __init__(self, accounts: AccountsResource) -> None:
def usage(self) -> UsageResourceWithStreamingResponse:
return UsageResourceWithStreamingResponse(self._accounts.usage)
+ @cached_property
+ def usage_analytics(self) -> UsageAnalyticsResourceWithStreamingResponse:
+ return UsageAnalyticsResourceWithStreamingResponse(self._accounts.usage_analytics)
+
@cached_property
def origins(self) -> OriginsResourceWithStreamingResponse:
return OriginsResourceWithStreamingResponse(self._accounts.origins)
@@ -157,6 +185,10 @@ def __init__(self, accounts: AsyncAccountsResource) -> None:
def usage(self) -> AsyncUsageResourceWithStreamingResponse:
return AsyncUsageResourceWithStreamingResponse(self._accounts.usage)
+ @cached_property
+ def usage_analytics(self) -> AsyncUsageAnalyticsResourceWithStreamingResponse:
+ return AsyncUsageAnalyticsResourceWithStreamingResponse(self._accounts.usage_analytics)
+
@cached_property
def origins(self) -> AsyncOriginsResourceWithStreamingResponse:
return AsyncOriginsResourceWithStreamingResponse(self._accounts.origins)
diff --git a/src/imagekitio/resources/accounts/usage.py b/src/imagekitio/resources/accounts/usage.py
index b35d3c9..3501e72 100644
--- a/src/imagekitio/resources/accounts/usage.py
+++ b/src/imagekitio/resources/accounts/usage.py
@@ -63,6 +63,12 @@ def get(
other words, the data covers the period starting from the specified start date
up to, but not including, the end date.
+ For an agency account, the returned usage is aggregated across the agency and
+ all of its child accounts that are billed to it.
+
+ The response is cached for 6 hours per account, date range and requested
+ metrics.
+
Args:
end_date: Specify a `endDate` in `YYYY-MM-DD` format. It should be after the `startDate`.
The difference between `startDate` and `endDate` should be less than 90 days.
@@ -136,6 +142,12 @@ async def get(
other words, the data covers the period starting from the specified start date
up to, but not including, the end date.
+ For an agency account, the returned usage is aggregated across the agency and
+ all of its child accounts that are billed to it.
+
+ The response is cached for 6 hours per account, date range and requested
+ metrics.
+
Args:
end_date: Specify a `endDate` in `YYYY-MM-DD` format. It should be after the `startDate`.
The difference between `startDate` and `endDate` should be less than 90 days.
diff --git a/src/imagekitio/resources/accounts/usage_analytics.py b/src/imagekitio/resources/accounts/usage_analytics.py
new file mode 100644
index 0000000..e2880fe
--- /dev/null
+++ b/src/imagekitio/resources/accounts/usage_analytics.py
@@ -0,0 +1,224 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union
+from datetime import date
+
+import httpx
+
+from ..._types import Body, Query, Headers, NotGiven, not_given
+from ..._utils import maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+from ...types.accounts import usage_analytics_get_params
+from ...types.accounts.usage_analytics_response import UsageAnalyticsResponse
+
+__all__ = ["UsageAnalyticsResource", "AsyncUsageAnalyticsResource"]
+
+
+class UsageAnalyticsResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> UsageAnalyticsResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/imagekit-developer/imagekit-python#accessing-raw-response-data-eg-headers
+ """
+ return UsageAnalyticsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> UsageAnalyticsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/imagekit-developer/imagekit-python#with_streaming_response
+ """
+ return UsageAnalyticsResourceWithStreamingResponse(self)
+
+ def get(
+ self,
+ *,
+ end_date: Union[str, date],
+ start_date: Union[str, date],
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> UsageAnalyticsResponse:
+ """
+ **Note:** This API is currently in beta.
+
+ Get the account analytics data between two dates. The response covers the period
+ from the start date to the end date, both dates inclusive. Both dates are
+ interpreted as UTC calendar days.
+
+ The returned data is scoped to the requesting account only. Unlike
+ `/v1/accounts/usage`, an agency account's analytics are not aggregated across
+ its child accounts.
+
+ The response is cached for 5 minutes per account and date range. Use
+ `generatedAt` to check how fresh the returned data is.
+
+ Args:
+ end_date: Specify an `endDate` in `YYYY-MM-DD` format, interpreted as a UTC calendar day.
+ It should be after the `startDate`. The difference between `startDate` and
+ `endDate` should be less than 90 days.
+
+ start_date: Specify a `startDate` in `YYYY-MM-DD` format, interpreted as a UTC calendar day.
+ It should be before the `endDate`. The difference between `startDate` and
+ `endDate` should be less than 90 days.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._get(
+ "/v1/accounts/usage-analytics",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "end_date": end_date,
+ "start_date": start_date,
+ },
+ usage_analytics_get_params.UsageAnalyticsGetParams,
+ ),
+ ),
+ cast_to=UsageAnalyticsResponse,
+ )
+
+
+class AsyncUsageAnalyticsResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncUsageAnalyticsResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/imagekit-developer/imagekit-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncUsageAnalyticsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncUsageAnalyticsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/imagekit-developer/imagekit-python#with_streaming_response
+ """
+ return AsyncUsageAnalyticsResourceWithStreamingResponse(self)
+
+ async def get(
+ self,
+ *,
+ end_date: Union[str, date],
+ start_date: Union[str, date],
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> UsageAnalyticsResponse:
+ """
+ **Note:** This API is currently in beta.
+
+ Get the account analytics data between two dates. The response covers the period
+ from the start date to the end date, both dates inclusive. Both dates are
+ interpreted as UTC calendar days.
+
+ The returned data is scoped to the requesting account only. Unlike
+ `/v1/accounts/usage`, an agency account's analytics are not aggregated across
+ its child accounts.
+
+ The response is cached for 5 minutes per account and date range. Use
+ `generatedAt` to check how fresh the returned data is.
+
+ Args:
+ end_date: Specify an `endDate` in `YYYY-MM-DD` format, interpreted as a UTC calendar day.
+ It should be after the `startDate`. The difference between `startDate` and
+ `endDate` should be less than 90 days.
+
+ start_date: Specify a `startDate` in `YYYY-MM-DD` format, interpreted as a UTC calendar day.
+ It should be before the `endDate`. The difference between `startDate` and
+ `endDate` should be less than 90 days.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._get(
+ "/v1/accounts/usage-analytics",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {
+ "end_date": end_date,
+ "start_date": start_date,
+ },
+ usage_analytics_get_params.UsageAnalyticsGetParams,
+ ),
+ ),
+ cast_to=UsageAnalyticsResponse,
+ )
+
+
+class UsageAnalyticsResourceWithRawResponse:
+ def __init__(self, usage_analytics: UsageAnalyticsResource) -> None:
+ self._usage_analytics = usage_analytics
+
+ self.get = to_raw_response_wrapper(
+ usage_analytics.get,
+ )
+
+
+class AsyncUsageAnalyticsResourceWithRawResponse:
+ def __init__(self, usage_analytics: AsyncUsageAnalyticsResource) -> None:
+ self._usage_analytics = usage_analytics
+
+ self.get = async_to_raw_response_wrapper(
+ usage_analytics.get,
+ )
+
+
+class UsageAnalyticsResourceWithStreamingResponse:
+ def __init__(self, usage_analytics: UsageAnalyticsResource) -> None:
+ self._usage_analytics = usage_analytics
+
+ self.get = to_streamed_response_wrapper(
+ usage_analytics.get,
+ )
+
+
+class AsyncUsageAnalyticsResourceWithStreamingResponse:
+ def __init__(self, usage_analytics: AsyncUsageAnalyticsResource) -> None:
+ self._usage_analytics = usage_analytics
+
+ self.get = async_to_streamed_response_wrapper(
+ usage_analytics.get,
+ )
diff --git a/src/imagekitio/types/accounts/__init__.py b/src/imagekitio/types/accounts/__init__.py
index 3d713db..60a394c 100644
--- a/src/imagekitio/types/accounts/__init__.py
+++ b/src/imagekitio/types/accounts/__init__.py
@@ -10,6 +10,9 @@
from .origin_request_param import OriginRequestParam as OriginRequestParam
from .origin_update_params import OriginUpdateParams as OriginUpdateParams
from .url_endpoint_response import URLEndpointResponse as URLEndpointResponse
+from .request_bandwidth_entry import RequestBandwidthEntry as RequestBandwidthEntry
+from .usage_analytics_response import UsageAnalyticsResponse as UsageAnalyticsResponse
from .url_endpoint_create_params import URLEndpointCreateParams as URLEndpointCreateParams
from .url_endpoint_list_response import URLEndpointListResponse as URLEndpointListResponse
from .url_endpoint_update_params import URLEndpointUpdateParams as URLEndpointUpdateParams
+from .usage_analytics_get_params import UsageAnalyticsGetParams as UsageAnalyticsGetParams
diff --git a/src/imagekitio/types/accounts/request_bandwidth_entry.py b/src/imagekitio/types/accounts/request_bandwidth_entry.py
new file mode 100644
index 0000000..ea245b6
--- /dev/null
+++ b/src/imagekitio/types/accounts/request_bandwidth_entry.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["RequestBandwidthEntry"]
+
+
+class RequestBandwidthEntry(BaseModel):
+ bandwidth_bytes: float = FieldInfo(alias="bandwidthBytes")
+ """Total bandwidth used in bytes."""
+
+ request_count: float = FieldInfo(alias="requestCount")
+ """Number of requests."""
diff --git a/src/imagekitio/types/accounts/usage_analytics_get_params.py b/src/imagekitio/types/accounts/usage_analytics_get_params.py
new file mode 100644
index 0000000..7d882ad
--- /dev/null
+++ b/src/imagekitio/types/accounts/usage_analytics_get_params.py
@@ -0,0 +1,27 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union
+from datetime import date
+from typing_extensions import Required, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+
+__all__ = ["UsageAnalyticsGetParams"]
+
+
+class UsageAnalyticsGetParams(TypedDict, total=False):
+ end_date: Required[Annotated[Union[str, date], PropertyInfo(alias="endDate", format="iso8601")]]
+ """Specify an `endDate` in `YYYY-MM-DD` format, interpreted as a UTC calendar day.
+
+ It should be after the `startDate`. The difference between `startDate` and
+ `endDate` should be less than 90 days.
+ """
+
+ start_date: Required[Annotated[Union[str, date], PropertyInfo(alias="startDate", format="iso8601")]]
+ """Specify a `startDate` in `YYYY-MM-DD` format, interpreted as a UTC calendar day.
+
+ It should be before the `endDate`. The difference between `startDate` and
+ `endDate` should be less than 90 days.
+ """
diff --git a/src/imagekitio/types/accounts/usage_analytics_response.py b/src/imagekitio/types/accounts/usage_analytics_response.py
new file mode 100644
index 0000000..131cf71
--- /dev/null
+++ b/src/imagekitio/types/accounts/usage_analytics_response.py
@@ -0,0 +1,488 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List
+from datetime import date, datetime
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .request_bandwidth_entry import RequestBandwidthEntry
+
+__all__ = [
+ "UsageAnalyticsResponse",
+ "Browser",
+ "BrowserByBandwidth",
+ "BrowserByRequest",
+ "Cache",
+ "Country",
+ "CountryByBandwidth",
+ "CountryByRequest",
+ "Device",
+ "DeviceByBandwidth",
+ "DeviceByRequest",
+ "ErrorReason",
+ "Extension",
+ "Format",
+ "FormatByBandwidth",
+ "FormatByRequest",
+ "StatusCode",
+ "Top404Asset",
+ "TopImages",
+ "TopImagesByBandwidth",
+ "TopImagesByRequest",
+ "TopImageTransforms",
+ "TopImageTransformsByBandwidth",
+ "TopImageTransformsByRequest",
+ "TopOtherAssets",
+ "TopOtherAssetsByBandwidth",
+ "TopOtherAssetsByRequest",
+ "TopReferrers",
+ "TopReferrersByBandwidth",
+ "TopReferrersByRequest",
+ "TopUserAgents",
+ "TopUserAgentsByBandwidth",
+ "TopUserAgentsByRequest",
+ "TopVideos",
+ "TopVideosByBandwidth",
+ "TopVideosByRequest",
+ "TopVideoTransforms",
+ "TopVideoTransformsByBandwidth",
+ "TopVideoTransformsByRequest",
+ "URLEndpoints",
+ "URLEndpointsByBandwidth",
+ "URLEndpointsByRequest",
+ "VideoProcessing",
+]
+
+
+class BrowserByBandwidth(RequestBandwidthEntry):
+ name: str
+ """Browser name (e.g. `Chrome`)."""
+
+
+class BrowserByRequest(RequestBandwidthEntry):
+ name: str
+ """Browser name (e.g. `Chrome`)."""
+
+
+class Browser(BaseModel):
+ """CDN traffic grouped by browser."""
+
+ by_bandwidth: List[BrowserByBandwidth] = FieldInfo(alias="byBandwidth")
+ """Top browsers sorted by bandwidth utilized."""
+
+ by_requests: List[BrowserByRequest] = FieldInfo(alias="byRequests")
+ """Top browsers sorted by request count."""
+
+
+class Cache(BaseModel):
+ """CDN cache hit, miss and error counts for the date range."""
+
+ error_count: float = FieldInfo(alias="errorCount")
+ """
+ Number of requests where the CDN encountered a cache error or exceeded capacity
+ while serving the response.
+ """
+
+ hit_count: float = FieldInfo(alias="hitCount")
+ """Number of requests served from cache, including full hits and revalidated hits."""
+
+ miss_count: float = FieldInfo(alias="missCount")
+ """
+ Number of requests that were not found in cache and had to be fetched from
+ origin.
+ """
+
+
+class CountryByBandwidth(RequestBandwidthEntry):
+ code: str
+ """ISO country code."""
+
+ name: str
+ """Country name."""
+
+
+class CountryByRequest(RequestBandwidthEntry):
+ code: str
+ """ISO country code."""
+
+ name: str
+ """Country name."""
+
+
+class Country(BaseModel):
+ """CDN traffic grouped by country."""
+
+ by_bandwidth: List[CountryByBandwidth] = FieldInfo(alias="byBandwidth")
+ """Top requesting countries sorted by total bandwidth utilized."""
+
+ by_requests: List[CountryByRequest] = FieldInfo(alias="byRequests")
+ """Top requesting countries sorted by request count."""
+
+
+class DeviceByBandwidth(RequestBandwidthEntry):
+ name: str
+ """Device category combined with operating system or vendor (e.g.
+
+ `Desktop - Windows PC`).
+ """
+
+
+class DeviceByRequest(RequestBandwidthEntry):
+ name: str
+ """Device category combined with operating system or vendor (e.g.
+
+ `Desktop - Windows PC`).
+ """
+
+
+class Device(BaseModel):
+ """CDN traffic grouped by device and operating system (e.g.
+
+ `Desktop - Apple Mac`, `Smartphone - Apple iPhone`).
+ """
+
+ by_bandwidth: List[DeviceByBandwidth] = FieldInfo(alias="byBandwidth")
+ """Top device/OS combinations sorted by bandwidth utilized."""
+
+ by_requests: List[DeviceByRequest] = FieldInfo(alias="byRequests")
+ """Top device/OS combinations sorted by request count."""
+
+
+class ErrorReason(BaseModel):
+ name: str
+ """Description of the error reason."""
+
+ request_count: float = FieldInfo(alias="requestCount")
+ """Number of requests that failed with this error reason."""
+
+
+class Extension(BaseModel):
+ name: str
+ """Extension identifier."""
+
+ operation_count: float = FieldInfo(alias="operationCount")
+ """Number of times this extension ran during the date range."""
+
+
+class FormatByBandwidth(RequestBandwidthEntry):
+ name: str
+ """MIME type (e.g. `image/webp`)."""
+
+
+class FormatByRequest(RequestBandwidthEntry):
+ name: str
+ """MIME type (e.g. `image/webp`)."""
+
+
+class Format(BaseModel):
+ """CDN traffic grouped by response `Content-Type`."""
+
+ by_bandwidth: List[FormatByBandwidth] = FieldInfo(alias="byBandwidth")
+ """Top content types sorted by bandwidth utilized."""
+
+ by_requests: List[FormatByRequest] = FieldInfo(alias="byRequests")
+ """Top content types sorted by request count."""
+
+
+class StatusCode(BaseModel):
+ name: str
+ """HTTP status code."""
+
+ request_count: float = FieldInfo(alias="requestCount")
+ """Number of requests that received this status code."""
+
+
+class Top404Asset(BaseModel):
+ name: str
+ """URL that returned a 404 response."""
+
+ request_count: float = FieldInfo(alias="requestCount")
+ """Number of requests to this URL that returned a 404 response."""
+
+
+class TopImagesByBandwidth(RequestBandwidthEntry):
+ name: str
+ """URL of the image asset."""
+
+
+class TopImagesByRequest(RequestBandwidthEntry):
+ name: str
+ """URL of the image asset."""
+
+
+class TopImages(BaseModel):
+ """Top image assets by traffic."""
+
+ by_bandwidth: List[TopImagesByBandwidth] = FieldInfo(alias="byBandwidth")
+ """Top image assets sorted by bandwidth utilized."""
+
+ by_requests: List[TopImagesByRequest] = FieldInfo(alias="byRequests")
+ """Top image assets sorted by request count."""
+
+
+class TopImageTransformsByBandwidth(RequestBandwidthEntry):
+ name: str
+ """Image transformation string (e.g. `tr:w-400,h-400`)."""
+
+
+class TopImageTransformsByRequest(RequestBandwidthEntry):
+ name: str
+ """Image transformation string (e.g. `tr:w-400,h-400`)."""
+
+
+class TopImageTransforms(BaseModel):
+ """Top image transformation strings by traffic."""
+
+ by_bandwidth: List[TopImageTransformsByBandwidth] = FieldInfo(alias="byBandwidth")
+ """Top image transformation strings sorted by bandwidth utilized."""
+
+ by_requests: List[TopImageTransformsByRequest] = FieldInfo(alias="byRequests")
+ """Top image transformation strings sorted by request count."""
+
+
+class TopOtherAssetsByBandwidth(RequestBandwidthEntry):
+ name: str
+ """URL of the non-image, non-video asset."""
+
+
+class TopOtherAssetsByRequest(RequestBandwidthEntry):
+ name: str
+ """URL of the non-image, non-video asset."""
+
+
+class TopOtherAssets(BaseModel):
+ """Top non-image, non-video assets by traffic."""
+
+ by_bandwidth: List[TopOtherAssetsByBandwidth] = FieldInfo(alias="byBandwidth")
+ """Top non-image, non-video assets sorted by bandwidth utilized."""
+
+ by_requests: List[TopOtherAssetsByRequest] = FieldInfo(alias="byRequests")
+ """Top non-image, non-video assets sorted by request count."""
+
+
+class TopReferrersByBandwidth(RequestBandwidthEntry):
+ name: str
+ """Referrer URL."""
+
+
+class TopReferrersByRequest(RequestBandwidthEntry):
+ name: str
+ """Referrer URL."""
+
+
+class TopReferrers(BaseModel):
+ """Top HTTP referrers by traffic."""
+
+ by_bandwidth: List[TopReferrersByBandwidth] = FieldInfo(alias="byBandwidth")
+ """Top HTTP referrers sorted by bandwidth utilized."""
+
+ by_requests: List[TopReferrersByRequest] = FieldInfo(alias="byRequests")
+ """Top HTTP referrers sorted by request count."""
+
+
+class TopUserAgentsByBandwidth(RequestBandwidthEntry):
+ name: str
+ """User agent string."""
+
+
+class TopUserAgentsByRequest(RequestBandwidthEntry):
+ name: str
+ """User agent string."""
+
+
+class TopUserAgents(BaseModel):
+ """Top user agents by traffic."""
+
+ by_bandwidth: List[TopUserAgentsByBandwidth] = FieldInfo(alias="byBandwidth")
+ """Top user agents sorted by bandwidth utilized."""
+
+ by_requests: List[TopUserAgentsByRequest] = FieldInfo(alias="byRequests")
+ """Top user agents sorted by request count."""
+
+
+class TopVideosByBandwidth(RequestBandwidthEntry):
+ name: str
+ """URL of the video asset."""
+
+
+class TopVideosByRequest(RequestBandwidthEntry):
+ name: str
+ """Full URL of the video asset (e.g. `https://ik.imagekit.io/demo/clip.mp4`)."""
+
+
+class TopVideos(BaseModel):
+ """Top video assets by traffic."""
+
+ by_bandwidth: List[TopVideosByBandwidth] = FieldInfo(alias="byBandwidth")
+ """Top video assets sorted by bandwidth utilized."""
+
+ by_requests: List[TopVideosByRequest] = FieldInfo(alias="byRequests")
+ """Top video assets sorted by request count."""
+
+
+class TopVideoTransformsByBandwidth(RequestBandwidthEntry):
+ name: str
+ """Video transformation string (e.g. `tr:h-720,f-mp4`)."""
+
+
+class TopVideoTransformsByRequest(RequestBandwidthEntry):
+ name: str
+ """Video transformation string (e.g. `tr:h-720,f-mp4`)."""
+
+
+class TopVideoTransforms(BaseModel):
+ """Top video transformation strings by traffic."""
+
+ by_bandwidth: List[TopVideoTransformsByBandwidth] = FieldInfo(alias="byBandwidth")
+ """Top video transformation strings sorted by bandwidth utilized."""
+
+ by_requests: List[TopVideoTransformsByRequest] = FieldInfo(alias="byRequests")
+ """Top video transformation strings sorted by request count."""
+
+
+class URLEndpointsByBandwidth(RequestBandwidthEntry):
+ name: str
+ """
+ URL endpoint name, or `Default` for traffic that does not match a named
+ endpoint.
+ """
+
+
+class URLEndpointsByRequest(RequestBandwidthEntry):
+ name: str
+ """
+ URL endpoint name, or `Default` for traffic that does not match a named
+ endpoint.
+ """
+
+
+class URLEndpoints(BaseModel):
+ """CDN traffic grouped by configured URL endpoint.
+
+ Traffic that does not match any named URL endpoint pattern is grouped under `Default`.
+ """
+
+ by_bandwidth: List[URLEndpointsByBandwidth] = FieldInfo(alias="byBandwidth")
+ """Top URL endpoints sorted by bandwidth utilized."""
+
+ by_requests: List[URLEndpointsByRequest] = FieldInfo(alias="byRequests")
+ """Top URL endpoints sorted by request count."""
+
+
+class VideoProcessing(BaseModel):
+ codec: str
+ """Video codec used for the output (e.g. `h264`, `av1`)."""
+
+ duration_seconds: float = FieldInfo(alias="durationSeconds")
+ """Total output duration, in seconds, for this resolution and codec combination."""
+
+ resolution: str
+ """Output resolution tier (e.g. `SD`, `HD`, `4K`)."""
+
+
+class UsageAnalyticsResponse(BaseModel):
+ bandwidth_bytes: float = FieldInfo(alias="bandwidthBytes")
+ """Total bandwidth, in bytes, utilized during the specified date range."""
+
+ browser: Browser
+ """CDN traffic grouped by browser."""
+
+ cache: Cache
+ """CDN cache hit, miss and error counts for the date range."""
+
+ country: Country
+ """CDN traffic grouped by country."""
+
+ device: Device
+ """CDN traffic grouped by device and operating system (e.g.
+
+ `Desktop - Apple Mac`, `Smartphone - Apple iPhone`).
+ """
+
+ end_date: date = FieldInfo(alias="endDate")
+ """End date of the computed analytics data."""
+
+ error_reasons: List[ErrorReason] = FieldInfo(alias="errorReasons")
+ """Request count grouped by origin error reason.
+
+ This covers failed origin fetches, such as an asset not found at origin or an
+ origin timeout. It is not the HTTP status code returned to the client, see
+ `statusCodes` for that.
+ """
+
+ extensions: List[Extension]
+ """Raw per-extension operation counts for the date range.
+
+ These are raw operation counts, not billable extension units. For billable
+ usage, use the `/v1/accounts/usage` endpoint.
+ """
+
+ format: Format
+ """CDN traffic grouped by response `Content-Type`."""
+
+ generated_at: datetime = FieldInfo(alias="generatedAt")
+ """Date and time when the analytics data was computed.
+
+ Use this to gauge how fresh the returned data is. The date and time is in
+ ISO8601 format.
+ """
+
+ request_count: float = FieldInfo(alias="requestCount")
+ """Total number of requests made during the specified date range."""
+
+ start_date: date = FieldInfo(alias="startDate")
+ """Start date of the computed analytics data."""
+
+ status_codes: List[StatusCode] = FieldInfo(alias="statusCodes")
+ """Request count grouped by HTTP status code."""
+
+ top404_assets: List[Top404Asset] = FieldInfo(alias="top404Assets")
+ """Top URLs that returned a 404 response."""
+
+ top_images: TopImages = FieldInfo(alias="topImages")
+ """Top image assets by traffic."""
+
+ top_image_transforms: TopImageTransforms = FieldInfo(alias="topImageTransforms")
+ """Top image transformation strings by traffic."""
+
+ top_other_assets: TopOtherAssets = FieldInfo(alias="topOtherAssets")
+ """Top non-image, non-video assets by traffic."""
+
+ top_referrers: TopReferrers = FieldInfo(alias="topReferrers")
+ """Top HTTP referrers by traffic."""
+
+ top_user_agents: TopUserAgents = FieldInfo(alias="topUserAgents")
+ """Top user agents by traffic."""
+
+ top_videos: TopVideos = FieldInfo(alias="topVideos")
+ """Top video assets by traffic."""
+
+ top_video_transforms: TopVideoTransforms = FieldInfo(alias="topVideoTransforms")
+ """Top video transformation strings by traffic."""
+
+ url_endpoints: URLEndpoints = FieldInfo(alias="urlEndpoints")
+ """CDN traffic grouped by configured URL endpoint.
+
+ Traffic that does not match any named URL endpoint pattern is grouped under
+ `Default`.
+ """
+
+ video_processing: List[VideoProcessing] = FieldInfo(alias="videoProcessing")
+ """
+ Raw observed video transcode output duration, in seconds, grouped by resolution
+ and codec. These are raw seconds, not billable Video Processing Units (VPU). For
+ billable VPU totals, use the `/v1/accounts/usage` endpoint.
+ """
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/tests/api_resources/accounts/test_usage_analytics.py b/tests/api_resources/accounts/test_usage_analytics.py
new file mode 100644
index 0000000..3881e5a
--- /dev/null
+++ b/tests/api_resources/accounts/test_usage_analytics.py
@@ -0,0 +1,99 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from imagekitio import ImageKit, AsyncImageKit
+from tests.utils import assert_matches_type
+from imagekitio._utils import parse_date
+from imagekitio.types.accounts import UsageAnalyticsResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestUsageAnalytics:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_get(self, client: ImageKit) -> None:
+ usage_analytics = client.accounts.usage_analytics.get(
+ end_date=parse_date("2019-12-27"),
+ start_date=parse_date("2019-12-27"),
+ )
+ assert_matches_type(UsageAnalyticsResponse, usage_analytics, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_get(self, client: ImageKit) -> None:
+ response = client.accounts.usage_analytics.with_raw_response.get(
+ end_date=parse_date("2019-12-27"),
+ start_date=parse_date("2019-12-27"),
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ usage_analytics = response.parse()
+ assert_matches_type(UsageAnalyticsResponse, usage_analytics, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_get(self, client: ImageKit) -> None:
+ with client.accounts.usage_analytics.with_streaming_response.get(
+ end_date=parse_date("2019-12-27"),
+ start_date=parse_date("2019-12-27"),
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ usage_analytics = response.parse()
+ assert_matches_type(UsageAnalyticsResponse, usage_analytics, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncUsageAnalytics:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_get(self, async_client: AsyncImageKit) -> None:
+ usage_analytics = await async_client.accounts.usage_analytics.get(
+ end_date=parse_date("2019-12-27"),
+ start_date=parse_date("2019-12-27"),
+ )
+ assert_matches_type(UsageAnalyticsResponse, usage_analytics, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_get(self, async_client: AsyncImageKit) -> None:
+ response = await async_client.accounts.usage_analytics.with_raw_response.get(
+ end_date=parse_date("2019-12-27"),
+ start_date=parse_date("2019-12-27"),
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ usage_analytics = await response.parse()
+ assert_matches_type(UsageAnalyticsResponse, usage_analytics, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_get(self, async_client: AsyncImageKit) -> None:
+ async with async_client.accounts.usage_analytics.with_streaming_response.get(
+ end_date=parse_date("2019-12-27"),
+ start_date=parse_date("2019-12-27"),
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ usage_analytics = await response.parse()
+ assert_matches_type(UsageAnalyticsResponse, usage_analytics, path=["response"])
+
+ assert cast(Any, response.is_closed) is True