Enhance usage API by exposing Analytics data#18
Conversation
…eakdowns for analytics based on requests and bandwidth
There was a problem hiding this comment.
Pull request overview
This PR enhances the /v1/accounts/usage API schema by expanding the response to include analytics metadata and detailed breakdowns, enabling SDKs to expose richer usage/analytics data to clients.
Changes:
- Adds
metadate-range information and new aggregate counters (requestCount, detailed VPU/extension usage). - Introduces reusable component schemas for breakdown rows and breakdown collections (
BreakdownItem,GeographyBreakdownItem,UsageBreakdowns, etc.). - Updates the endpoint example payload to reflect the richer analytics response, including top breakdowns sorted by requests and bandwidth.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| topBreakdownsByBandwidth: | ||
| allOf: | ||
| - $ref: "#/components/schemas/UsageBreakdowns" | ||
| description: Top breakdowns ranked by bandwidth. |
|
|
||
| UsageMeta: | ||
| type: object | ||
| description: The date range the usage data covers. |
| requestCount: | ||
| type: integer | ||
| description: Number of requests made. | ||
| videoProcessingUnitsUsage: |
There was a problem hiding this comment.
requestCount at the root is fine as it is an overall aggregation like bandwidthBytes, however everything else should be inside a new property named analytics.
So I suggest this shape, similar as yours with slight modifications.
Mainly mentioned integer in sdk as it would not be float, just verify we will be good as per response shape. If we sent double, sdk would break or cast incorrectly.
Then got rid of meta and GeographyBreakdownItem.
Consistently use requestCount instead of requests
Removing total from extensions breakdown, we have that at root already.
Removed cache as it was not clear to me what are we sending there. What is useful for user? Same for resultType.
We have to think about the case when we add new keys e.g. in extension breakdown. We cant send breaking change then. So either we start with generic object to being with or test it out in advance. Adding new keys in most sdk should not be breaking. Still better to include additionalProperty:true in oas in all nested objects? Other way is that we send it like array of {item:"name here e.g. 4K or extension name", value: integer}. This would cover both VPU and extension breakdown.
4K 8K 16K might not be good key names.
URL-endpoint wise data is missing but asked by customer. Mentioned in ticket
interface UsageReport {
bandwidthBytes: integer;
mediaLibraryStorageBytes: integer;
videoProcessingUnitsCount: integer;
extensionUnitsCount: integer;
originalCacheStorageBytes: integer;
requestCount: integer; // new property at root
analytics: { // new property at root
VPU: VPUBreakdown
extensions: ExtensionBreakdown
breakdowns: {
byBandwidth: TopBreakdowns;
byRequest: TopBreakdowns;
}
}
}
interface VPUBreakdown {
SD: integer;
HD: integer;
"4K": integer;
"8K": integer;
"16K": integer;
}
interface ExtensionBreakdown {
"aws-auto-tagging": integer;
"google-auto-tagging": integer;
"ai-auto-description": integer;
ikremovebg: integer;
"remove-bg": integer;
upscale: integer;
retouch: integer;
genvar: integer;
genfill: integer;
genimg: integer;
dropshadow: integer;
"change-bg": integer;
edit: integer;
"ai-tasks": integer;
"ai-video-transcription": integer;
"creative-banner": integer;
}
interface BreakdownItem {
value: string;
requestCount: integer;
bandwidthBytes: integer;
}
export interface TopBreakdowns {
statusCodes: BreakdownItem[];
formats: BreakdownItem[];
geography: BreakdownItem[];
devices: BreakdownItem[];
imageTransformations: BreakdownItem[];
videoTransformations: BreakdownItem[];
image: BreakdownItem[];
video: BreakdownItem[];
others: BreakdownItem[];
referral: BreakdownItem[];
userAgents: BreakdownItem[];
}
…iled breakdowns for improved data insights
Proposing these points
topBreakdownsByRequestandtopBreakdownsByBandwidthas the two top-level sort buckets, visible duplication but a cleaner approach (we can also start accepting a sorting key to return bandwidth and requests separatly rather than a monolith response with both kinds of sorting )Old response of the api
{ "bandwidthBytes": 18273487, "mediaLibraryStorageBytes": 697650923, "videoProcessingUnitsCount": 232, "extensionUnitsCount": 0, "originalCacheStorageBytes": 0 }Proposed new New Response Types
Proposed new New Response Example
{ "meta": { "from": "2026-06-01", "to": "2026-06-26" }, "bandwidthBytes": 18273487, "mediaLibraryStorageBytes": 697650923, "videoProcessingUnitsCount": 232, "extensionUnitsCount": 0, "originalCacheStorageBytes": 0, "requestCount": 160, "videoProcessingUnitsUsage": { "SD": 232, "HD": 0, "4K": 0, "8K": 0, "16K": 0 }, "extensionUnitsUsage": { "total": 0, "aws-auto-tagging": 0, "google-auto-tagging": 0, "ai-auto-description": 0, "ikremovebg": 0, "remove-bg": 0, "upscale": 0, "retouch": 0, "genvar": 0, "genfill": 0, "genimg": 0, "dropshadow": 0, "change-bg": 0, "edit": 0, "ai-tasks": 0, "ai-video-transcription": 0, "creative-banner": 0 }, "topBreakdownsByRequest": { "statusCodes": [ { "value": "200", "requests": 148, "bandwidthBytes": 13924675 }, { "value": "404", "requests": 7, "bandwidthBytes": 4250 }, { "value": "206", "requests": 4, "bandwidthBytes": 4344030 } ], "formats": [ { "value": "image/webp", "requests": 119, "bandwidthBytes": 5261379 }, { "value": "image/jpeg", "requests": 20, "bandwidthBytes": 4570132 }, { "value": "video/webm", "requests": 8, "bandwidthBytes": 6727289 } ], "cache": [ { "value": "Miss", "requests": 96, "bandwidthBytes": 12276318 }, { "value": "Hit", "requests": 56, "bandwidthBytes": 5992387 }, { "value": "Error", "requests": 8, "bandwidthBytes": 4782 } ], "geography": [ { "value": "India", "countryCode": "IN", "requests": 136, "bandwidthBytes": 11218939 }, { "value": "Singapore", "countryCode": "SG", "requests": 22, "bandwidthBytes": 5669799 } ], "devices": [ { "value": "Desktop", "requests": 139, "bandwidthBytes": 16644941 }, { "value": "Smartphone", "requests": 17, "bandwidthBytes": 237432 } ], "transformations": [ { "value": "n-ik_ml_thumbnail", "requests": 80, "bandwidthBytes": 868433 } ], "images": [ { "value": "https://ik.imagekit.io/your_id/photo.jpeg", "requests": 80, "bandwidthBytes": 868433 } ], "videos": [ { "value": "https://ik.imagekit.io/your_id/clip.mp4", "requests": 8, "bandwidthBytes": 6727289 } ], "others": [ { "value": "https://ik.imagekit.io/your_id/design.psd", "requests": 2, "bandwidthBytes": 1389412 } ], "referral": [], "videoTransformations": [], "resultType": [ { "value": "Miss", "requests": 96, "bandwidthBytes": 12276318 }, { "value": "Hit", "requests": 56, "bandwidthBytes": 5992387 }, { "value": "Error", "requests": 8, "bandwidthBytes": 4782 } ], "userAgents": [ { "value": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36", "requests": 103, "bandwidthBytes": 10238534 }, { "value": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36", "requests": 22, "bandwidthBytes": 5669799 } ] }, "topBreakdownsByBandwidth": { "statusCodes": [ { "value": "200", "requests": 148, "bandwidthBytes": 13924675 }, { "value": "206", "requests": 4, "bandwidthBytes": 4344030 }, { "value": "404", "requests": 7, "bandwidthBytes": 4250 } ], "formats": [ { "value": "video/webm", "requests": 8, "bandwidthBytes": 6727289 }, { "value": "image/webp", "requests": 119, "bandwidthBytes": 5261379 }, { "value": "image/jpeg", "requests": 20, "bandwidthBytes": 4570132 } ], "cache": [ { "value": "Miss", "requests": 96, "bandwidthBytes": 12276318 }, { "value": "Hit", "requests": 56, "bandwidthBytes": 5992387 }, { "value": "Error", "requests": 8, "bandwidthBytes": 4782 } ], "geography": [ { "value": "India", "countryCode": "IN", "requests": 136, "bandwidthBytes": 11218939 }, { "value": "Singapore", "countryCode": "SG", "requests": 22, "bandwidthBytes": 5669799 } ], "devices": [ { "value": "Desktop", "requests": 139, "bandwidthBytes": 16644941 }, { "value": "Smartphone", "requests": 17, "bandwidthBytes": 237432 } ], "transformations": [ { "value": "n-ik_ml_thumbnail", "requests": 80, "bandwidthBytes": 868433 } ], "images": [ { "value": "https://ik.imagekit.io/your_id/photo.jpeg", "requests": 80, "bandwidthBytes": 868433 } ], "videos": [ { "value": "https://ik.imagekit.io/your_id/clip.mp4", "requests": 8, "bandwidthBytes": 6727289 } ], "others": [ { "value": "https://ik.imagekit.io/your_id/design.psd", "requests": 2, "bandwidthBytes": 1389412 } ], "referral": [], "videoTransformations": [], "resultType": [ { "value": "Miss", "requests": 96, "bandwidthBytes": 12276318 }, { "value": "Hit", "requests": 56, "bandwidthBytes": 5992387 }, { "value": "Error", "requests": 8, "bandwidthBytes": 4782 } ], "userAgents": [ { "value": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36", "requests": 103, "bandwidthBytes": 10238534 }, { "value": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36", "requests": 22, "bandwidthBytes": 5669799 } ] } }