Skip to main content

Command Palette

Search for a command to run...

APEX: OCI Cost Usage

Grouping REST Response for use in Summary Reports and Dashboards

Updated
•9 min read
APEX: OCI Cost Usage
S

I work with software but it does not define me and my constant is change and I live a life of evolution. Learning, adapting, forgetting, re-learning, repeating I am not a Developer, I simply use software tools to solve interesting challenges and implement different use cases.

Cloud positioning and pressures are here. Deals have been signed, enterprise services migrations have been budgeted, and the journeys are well on their way.

The initial fears to "Trust" service and cloud providers may have been allayed to some degree but do providers really give the cost benefit they touted, do budgets match the consumption?

About this Post

This month, curiously, a few customers asked myself, on separate projects, to provide some answers to these questions. Although the console does provide reports and dashboards, it can be confusing and unclear to interpret.

The task at hand was to visualization this information in APEX, by consuming the OCI Cost Usage APIs. OCI does offer several SDK options, including the PL/SQL SDK Usage API.

I'll tackle the first and simplest of the two, the REST API.

OCI Billing and Cost Management Overview

The documentation states that:

💡
Oracle Cloud Infrastructure provides various billing and cost management tools that make it easy to manage your service costs.
💡
You can estimate costs, create budgets to set spending thresholds, view cost reports, and visualize your spending with charts and reports.
💡
You can view your subscription details, invoices, usage statements, payment history, manage your payment method, and earn rewards.

It even has a OCI FinOps Hub and a Cloud Advisor, so why build it in APEX? 🤔

Policy management, segregated views, access to the cloud console in itself could contribute to the decision to build a dedicated app but I don't have to debate such things. I simply need to support the customers who choice to do so.

Let's get into it ...

OCI Cost Usage API

The Usage API follows the typical OCI REST API format, it has a base endpoint per region, for me, in the Frankfurt region, it is => https://usageapi.eu-frankfurt-1.oci.oraclecloud.com

It provides APIs to few the calculated Carbon Emission for your services, how much of the Energy was Clean and Green, and of course the Usage Summaries, which is what I am interested in.

This returns the summarized totals across the tenant and can be filtered to match you requirement.

💡
Check out the TypeScript SDK example. It has a full JSON Request Body example to help understand how to use the supported parameters.

You will have to navigate the OCI documentation to assemble all of the types for the request, and if you are struggling, check out my previous blog on how to understand and RTF=>OCI-M.

OCI Cost Usage API APEX REST Templates

Required Attributes Only Template

From the request body details there are only three required attributes

Only four attributes are required: granularity, tenantId, timeUsageEnded, & timeUsageStarted

{
   "granularity": "DAILY",
   "tenantId": "#TENANTID#",
   "timeUsageEnded": "#USAGEENDED#",
   "timeUsageStarted": "#USAGESTARTED#"
}

This is not recommended as the response will not be very useful.

{
  "groupBy" : null,
  "items" :
  [
    {
      "tenantId" : null,
      "tenantName" : null,
      "compartmentId" : null,
      "compartmentPath" : null,
      "compartmentName" : null,
      "service" : null,
      "resourceName" : null,
      "resourceId" : null,
      "region" : null,
      "ad" : null,
      "weight" : null,
      "shape" : null,
      "skuPartNumber" : null,
      "skuName" : null,
      "unit" : null,
      "discount" : null,
      "listRate" : null,
      "platform" : null,
      "timeUsageStarted" : "2026-03-20T00:00:00.000Z",
      "timeUsageEnded" : "2026-03-21T00:00:00.000Z",
      "computedAmount" : 2611.391053413421,
      "computedQuantity" : 809275.68046217912,
      "attributedCost" : "2611.391053413421",
      "attributedUsage" : "809275.680462179313",
      "overagesFlag" : null,
      "unitPrice" : null,
      "currency" : "USD",
      "subscriptionId" : null,
      "overage" : null,
      "isForecast" : false,
      "tags" :
      [
        {
          "namespace" : null,
          "key" : null,
          "value" : null
        }
      ]
    },
    {
      "tenantId" : null,
      "tenantName" : null,
      "compartmentId" : null,
      "compartmentPath" : null,
      "compartmentName" : null,
      "service" : null,
      "resourceName" : null,
      "resourceId" : null,
      "region" : null,
      .....
}

The previous response was not very useful as most of the column values were null. The APIs is used to provide summary aggregates, but without the groupBy clause, it basically does nothing.

I did not expect this and would not find this API useful without the aggregation, and hence groupBy should be required, but it is not.

If a complete view is what is required, then several passes will be be needed as only 4 items are supported in the groupBy attribute.

For example, if I wanted to know the cost per tenant, across regions, per service, across LOB compartments and budget or cost Tag, I couldn't.

I would need to get higher level values first, then make another pass for the lower attributes while including some common attribute across the queries.

{
   "compartmentDepth": 7,
   "tenantId": "#TENANTID#",
   "timeUsageEnded": "#USAGEENDED#",
   "timeUsageStarted": "#USAGESTARTED#",
   "granularity": "DAILY",
   "groupBy": ["tagValue","service","compartmentName","compartmentId"],
   "filter": {
      "operator": "OR",
      "tags": [{
         "key": "CreatedBy",
         "namespace": "Default_Tags",
         "value": "#DOMAINUSERNAME#"
      }]
   }
}

In the provided example, I want to have the Tag values, which is nested in the response.

Strangely, not all columns return a value and similarly the groupBy does not support all of the columns that are available.

You can see that the resourceName and even for resources like Compute the ad, are always null

{
  "groupBy" :
  [
    "tagValue",
    "service",
    "compartmentName",
    "compartmentId"
  ],
  "items" :
  [
    {
      "tenantId" : null,
      "tenantName" : null,
      "compartmentId" : "ocid1.compartment.oc1..OCID",
      "compartmentPath" : null,
      "compartmentName" : "SydneyNurse",
      "service" : "Database",
      "resourceName" : null,
      "resourceId" : null,
      "region" : null,
      "ad" : null,
      "weight" : null,
      "shape" : null,
      "skuPartNumber" : null,
      "skuName" : null,
      "unit" : null,
      "discount" : null,
      "listRate" : null,
      "platform" : null,
      "timeUsageStarted" : "2026-03-04T00:00:00.000Z",
      "timeUsageEnded" : "2026-03-05T00:00:00.000Z",
      "computedAmount" : 8.69055801355,
      "computedQuantity" : 1328.978745298711,
      "attributedCost" : "8.69055801355",
      "attributedUsage" : "1328.978745298711",
      "overagesFlag" : null,
      "unitPrice" : null,
      "currency" : "USD",
      "subscriptionId" : null,
      "overage" : null,
      "isForecast" : false,
      "tags" :
      [
        {
          "namespace" : null,
          "key" : null,
          "value" : "bigDomain/sydney.nurse@oracle.com"
        }
      ]
    },
    {
      "tenantId" : null,
      "tenantName" : null,
      "compartmentId" : "ocid1.compartment.oc1..OCID",
      "compartmentPath" : null,
      "compartmentName" : "SydneyNurse",
      "service" : "Database",
      "resourceName" : null,
      "resourceId" : null,
      "region" : null,
      ....
}

I also added a filter example but as I noted earlier the TypeScript example is very good.

💡
Think about what you need to report on and visualize to determine the form of the request template

The REST Data Source

Manually create the REST Data Source, set it up with a POST method and provide the Request Template.

Parameterize it as required and synchronize it with the Request Body.

💡
Content-Type is set to application/json, required and static value

Provide some default values that will allow you to test the POST method.

💡
Date Format must be: 'YYYY-MM-DD"T"HH24:MI:SS".000Z"' for example: 2026-03-01T17:00:00.000000-07:00 Note that the Minutes and Second divisions are 00:00.000000. The API did not support anything lower than an Hour.

**This could be due to my level of granularity**

Building Visualizations

For the above screenshot, I created a blank page with two Page Items for the Start and End Dates.

I used YYYY-MM-DD HH24:MI:SS for my format but this can be anything or null to take the app defaults

I set my defaults for the previous Month Start and End day.

BEGIN
    :P102_START_DATE :=       
        TO_CHAR(TRUNC(ADD_MONTHS(TRUNC(CAST(CURRENT_TIMESTAMP           
        AS DATE), 'MM'), -1), 'MM'),'YYYY-MM-DD HH24:MI:SS');

    :P102_END_DATE :=
        TO_CHAR(TRUNC(CAST(CURRENT_TIMESTAMP 
        AS DATE), 'MM'), 'YYYY-MM-DD HH24:MI:SS');
END;

I used the Default property but shown here in a single PL/SQL block.

REST Source Parameters

The Dates for the REST Source are the key attributes that need formatting. This was the trickiest area to get correct and with the default values, the Page Items, need to be formatted as required: 'YYYY-MM-DD"T"HH24:MI:SS".000Z"'

Set the Parameters to use a PL/SQL Expression

-- USAGESTARTED
to_char(to_timestamp(:P102_START_DATE, 'YYYY-MM-DD HH24:MI:SS'),'YYYY-MM-DD"T"HH24:MI:SS".000Z"')

-- USAGEENDED
to_char(to_timestamp(:P102_END_DATE, 'YYYY-MM-DD HH24:MI:SS'),'YYYY-MM-DD"T"HH24:MI:SS".000Z"')

Charts

For the Totals per Day, I used an Area Chart sourcing the REST Source. I submitted the start and end date page items and set the Data Profile to the Tags nested rows.

I would like the values to be stacked, gaps filled and I set the Time Axis Type to Automatic.

I used a Donut chart for the Totals per Service, nothing special in the settings

Additional Groupings

While inspecting the response, you will notice that the summaries are aggregated at the granularity level, so for the chart, some additional rollup was required.

Using the Local Post Processing option, I adapt the response to my requirements

select
    SERVICE,
    SUM(ATTRIBUTEDCOST) COST,
    TIMEUSAGEENDED,
    TIMEUSAGESTARTED
from #APEX$SOURCE_DATA#

GROUP BY SERVICE, TIMEUSAGESTARTED, TIMEUSAGEENDED
ORDER BY SERVICE

I used the same for both regions but adapt it to suite the report and visualization.

Series

Using the post processing columns, I set the region series as follows:

Total Costs per Day

Column Mapping:

  • Series Name: SERVICE

  • Label: TIMEUSAGESTARTED

  • Value: COST

Total Costs per Series

Column Mapping:

  • Series Name: SERVICE

  • Label: SERVICE

  • Value: COST

Conclusion

Consuming OCI REST APIs, stays the same, the process to navigate and understand the documentation has not changed.

The Cost Usage API is a little different as data will be aggregated at source using the groupBy attribute. It only supports a single HTTP POST method, so auto-discovery is not the best option when creating the REST Data Source, use Manual.

Large customer deployments that span multiple, tenants and regions, may need to make several passes to get a complete picture at the right level for reporting requirements.

Synchronizing this data for monthly reports would be advantageous and provide users with faster screen responses by building on top of tables and views for the additional aggregation.

I've not delved into the Forecasting but this is a nice option for budgeting discussion to estimate overages or during the next round of negotiations.

APEX OCI Rest Integration

Part 1 of 8

A series of articles related to integrating APEX with OCI Services over REST. The set of services are Infrastructure or platform related. Oracle SaaS and Fusion services will be in the APEX for Fusion Extension series.

Up next

APEX: Integrating with Oracle Digital Assistant Dynamic Entities

Working with Dynamic Entities in Oracle Digital Assistant from Oracle APEX and Custom Database Applications