AWS Price List API

My name is Satish Kumar Jaiswal. I work for Hitachi Ltd., R&D Group, Services Computing Research Department. In this article, I will explain about how to make a query to the AWS Price List API from the command line using aws-cli. In particular, how to fetch the price list of General Purpose EBS Volume. 

# Introduction

If you want to obtain the price of AWS products and services programmatically, then there are two basic APIs for doing so. These are:

 1. [Query API](https://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/using-pelong.html)
 2. [Bulk API](https://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/using-ppslong.html) 

The end-points for these APIs are the following.

- https://api.pricing.us-east-1.amazonaws.com
- https://api.pricing.ap-south-1.amazonaws.com 

# 1. Query API

#### Introduction 
The [Query API](https://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/using-pelong.html) allows you to obtain up-to-date information about AWS products, services and their prices using your terminal. 
 
As of now, there are 4 commands that can be used to query the AWS Price List API.

 1. aws pricing describe-services  
 2. aws pricing get-attribute-values 
 3. aws pricing get-products 
 4. aws pricing help  

You can open the corresponding man-pages using the help command. 
Eg.

```
$ aws pricing describe-services help
```

#### Pre-requisite
You must have an AWS account and **[aws-cli](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-welcome.html)** installed on your system before you can start using it.


#### Some Useful Commands 

**1. aws pricing describe-services**

 - AWS has hundreds of services. So before we can start querying the API, we must be sure which AWS services we are interested in. 
 - This command fetches an array of all the AWS services in JSON format as shown below. I have just shown the result for **AmazonEC2**. 

```
$ aws pricing describe-services
{
    "Services": [
        .
        .
        . 
        {
            "ServiceCode": "AmazonEC2",
            "AttributeNames": [
                "volumeType",
                "maxIopsvolume",
                "instance",
                "instanceCapacity10xlarge",
                "locationType",
                "toLocationType",
                "instanceFamily",
                "operatingSystem",
                "clockSpeed",
                "LeaseContractLength",
                "ecu",
                "networkPerformance",
                "instanceCapacity8xlarge",
                "group",
                "maxThroughputvolume",
                "gpuMemory",
                "ebsOptimized",
                "maxVolumeSize",
                "gpu",
                "processorFeatures",
                "intelAvxAvailable",
                "instanceCapacity4xlarge",
                "servicecode",
                "groupDescription",
                "elasticGraphicsType",
                "volumeApiName",
                "processorArchitecture",
                "fromLocation",
                "physicalCores",
                "productFamily",
                "fromLocationType",
                "enhancedNetworkingSupported",
                "intelTurboAvailable",
                "memory",
                "dedicatedEbsThroughput",
                "vcpu",
                "OfferingClass",
                "instanceCapacityLarge",
                "capacitystatus",
                "termType",
                "storage",
                "toLocation",
                "intelAvx2Available",
                "storageMedia",
                "physicalProcessor",
                "provisioned",
                "servicename",
                "PurchaseOption",
                "instancesku",
                "productType",
                "instanceCapacity18xlarge",
                "instanceType",
                "tenancy",
                "usagetype",
                "normalizationSizeFactor",
                "instanceCapacity2xlarge",
                "instanceCapacity16xlarge",
                "maxIopsBurstPerformance",
                "instanceCapacity32xlarge",
                "instanceCapacity12xlarge",
                "instanceCapacityXlarge",
                "licenseModel",
                "currentGeneration",
                "preInstalledSw",
                "transferType",
                "location",
                "instanceCapacity9xlarge",
                "instanceCapacity24xlarge",
                "instanceCapacityMedium",
                "operation",
                "resourceType"
            ]
        },
        .
        .
        .
    ],
    "FormatVersion": "aws_v1"
}
```

**2. aws pricing get-products --service-code AmazonEC2 --max-items 1**

 - After we are sure of which AWS service we are interested in (AmazonEC2 in this case), we would like to get the price list for that service. 
 - This command fetches an array of the price list for the AWS service under consideration (AmazonEC2 in this case).  Note that I have supplied the argument **--max-items 1** so that only one record is fetched. If you don't supply this argument, all of the records will be fetched which might take several minutes.
 - Each element in the **PriceList** array has 4 keys. We are only interested in 2 of them.
    - **product**: It contains detailed information about the products under the AWS service considered (AmazonEC2 in this case). 
    - **terms**: It contains detailed information about the price.
 - Note that sed and jq commands are used only for displaying the results in a nice JSON formatted manner.

```
$ aws pricing get-products --service-code AmazonEC2 --max-items 1 | sed -e 's/\\//g' | sed -e 's/"{/{/g' | sed -e 's/}"/}/g' | jq '.'
{
  "PriceList": [
    {
      "product": {
        "productFamily": "Compute Instance (bare metal)",
        "attributes": {
          "enhancedNetworkingSupported": "Yes",
          "intelTurboAvailable": "Yes",
          "memory": "768 GiB",
          "dedicatedEbsThroughput": "14000 Mbps",
          "vcpu": "96",
          "capacitystatus": "UnusedCapacityReservation",
          "locationType": "AWS Region",
          "storage": "8 x 7500 NVMe SSD",
          "instanceFamily": "Storage optimized",
          "operatingSystem": "SUSE",
          "intelAvx2Available": "Yes",
          "physicalProcessor": "Intel Xeon Platinum 8175",
          "clockSpeed": "3.1 GHz",
          "ecu": "NA",
          "networkPerformance": "100 Gigabit",
          "servicename": "Amazon Elastic Compute Cloud",
          "instancesku": "3MFG4YWWT6SPWHET",
          "instanceType": "i3en.metal",
          "tenancy": "Shared",
          "usagetype": "EU-UnusedBox:i3en.metal",
          "normalizationSizeFactor": "192",
          "intelAvxAvailable": "Yes",
          "servicecode": "AmazonEC2",
          "licenseModel": "No License required",
          "currentGeneration": "Yes",
          "preInstalledSw": "NA",
          "location": "EU (Ireland)",
          "processorArchitecture": "64-bit",
          "operation": "RunInstances:000g"
        },
        "sku": "2228VZNAASRCHSES"
      },
      "serviceCode": "AmazonEC2",
      "terms": {
        "OnDemand": {
          "2228VZNAASRCHSES.JRTCKXETXF": {
            "priceDimensions": {
              "2228VZNAASRCHSES.JRTCKXETXF.6YS6EN2CT7": {
                "unit": "Hrs",
                "endRange": "Inf",
                "description": "$12.15 per Unused Reservation SUSE i3en.metal Instance Hour",
                "appliesTo": [],
                "rateCode": "2228VZNAASRCHSES.JRTCKXETXF.6YS6EN2CT7",
                "beginRange": "0",
                "pricePerUnit": {
                  "USD": "12.1500000000"
                }
              }
            },
            "sku": "2228VZNAASRCHSES",
            "effectiveDate": "2019-12-01T00:00:00Z",
            "offerTermCode": "JRTCKXETXF",
            "termAttributes": {}
          }
        }
      },
      "version": "20191203220726",
      "publicationDate": "2019-12-03T22:07:26Z"
    }
  ],
  "FormatVersion": "aws_v1",
  "NextToken": "eyJOZXh0VG9rZW4iOiBudWxsLCAiYm90b190cnVuY2F0ZV9hbW91bnQiOiAxfQ=="
}
```

**3. aws pricing get-attribute-values --service-code AmazonEC2 --attribute-name productFamily**

 - There are several products under a given AWS service. Eg. Some of the products under AmazonEC2 are Compute Instance (bare metal), Compute Instance, Storage, IP Address, NAT Gateway, etc.
 - In order to get a list of these products, we can use the above command. 
 - It actually fetches all the values for a particular attribute. Thus, there can be other use cases of this command as well.

```
$ aws pricing get-attribute-values --service-code AmazonEC2 --attribute-name productFamily
{
    "AttributeValues": [
        {
            "Value": "CPU Credits"
        },
        {
            "Value": "Compute Instance (bare metal)"
        },
        {
            "Value": "Compute Instance"
        },
        {
            "Value": "Data Transfer"
        },
        {
            "Value": "Dedicated Host"
        },
        {
            "Value": "EBS direct API Requests"
        },
        {
            "Value": "Elastic Graphics"
        },
        {
            "Value": "Fast Snapshot Restore"
        },
        {
            "Value": "Fee"
        },
        {
            "Value": "IP Address"
        },
        {
            "Value": "Load Balancer-Application"
        },
        {
            "Value": "Load Balancer-Network"
        },
        {
            "Value": "Load Balancer"
        },
        {
            "Value": "NAT Gateway"
        },
        {
            "Value": "Storage Snapshot"
        },
        {
            "Value": "Storage"
        },
        {
            "Value": "System Operation"
        }
    ]
}
```

**4. aws pricing get-products --service-code AmazonEC2 --max-items 1 --filters Type=TERM_MATCH,Field=productFamily,Value=Storage Type=TERM_MATCH,Field=volumeType,Value=General\ Purpose**


 - In this part, I will show you how to get the price list for General Purpose EBS Volume (i.e. gp2).
 - From the previous example, we know that the productFamily for EBS is Storage.
 - Similarly, using the command in the previous example, we can list the volumeType for EBS as below.

```
$ aws pricing get-attribute-values --service-code AmazonEC2 --attribute-name volumeType 
{
    "AttributeValues": [
        {
            "Value": "Cold HDD"
        },
        {
            "Value": "General Purpose"
        },
        {
            "Value": "Magnetic"
        },
        {
            "Value": "Provisioned IOPS"
        },
        {
            "Value": "Throughput Optimized HDD"
        }
    ]
}
```
  
 - Thus, using a **filter**, we can query the price list for General Purpose EBS Volume (i.e. gp2) for which productFamily is Storage and volumeType is General Purpose.
 - Note that sed and jq commands are used only for displaying the results in a nice JSON formatted manner.

```
$ aws pricing get-products --service-code AmazonEC2 --max-items 1 --filters Type=TERM_MATCH,Field=productFamily,Value=Storage Type=TERM_MATCH,Field=volumeApiName,Value=gp2 | sed -e 's/\\//g' | sed -e 's/"{/{/g' | sed -e 's/}"/}/g' | jq '.'
{
  "PriceList": [
    {
      "product": {
        "productFamily": "Storage",
        "attributes": {
          "maxThroughputvolume": "40 - 90 MB/sec",
          "volumeType": "Magnetic",
          "maxIopsvolume": "40 - 200",
          "usagetype": "EBS:VolumeUsage",
          "locationType": "AWS Region",
          "maxVolumeSize": "1 TiB",
          "maxIopsBurstPerformance": "Hundreds",
          "storageMedia": "HDD-backed",
          "servicecode": "AmazonEC2",
          "volumeApiName": "standard",
          "location": "US East (N. Virginia)",
          "servicename": "Amazon Elastic Compute Cloud",
          "operation": ""
        },
        "sku": "269VXUCZZ7E6JNXT"
      },
      "serviceCode": "AmazonEC2",
      "terms": {
        "OnDemand": {
          "269VXUCZZ7E6JNXT.JRTCKXETXF": {
            "priceDimensions": {
              "269VXUCZZ7E6JNXT.JRTCKXETXF.6YS6EN2CT7": {
                "unit": "GB-Mo",
                "endRange": "Inf",
                "description": "$0.05 per GB-month of Magnetic provisioned storage - US East (Northern Virginia)",
                "appliesTo": [],
                "rateCode": "269VXUCZZ7E6JNXT.JRTCKXETXF.6YS6EN2CT7",
                "beginRange": "0",
                "pricePerUnit": {
                  "USD": "0.0500000000"
                }
              }
            },
            "sku": "269VXUCZZ7E6JNXT",
            "effectiveDate": "2019-12-01T00:00:00Z",
            "offerTermCode": "JRTCKXETXF",
            "termAttributes": {}
          }
        }
      },
      "version": "20191203220726",
      "publicationDate": "2019-12-03T22:07:26Z"
    }
  ],
  "FormatVersion": "aws_v1",
  "NextToken": "eyJOZXh0VG9rZW4iOiBudWxsLCAiYm90b190cnVuY2F0ZV9hbW91bnQiOiAxfQ=="
} 
```

# 2. Bulk API

The [Bulk API](https://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/using-ppslong.html) actually provides an URL from where you can download up-to-date information on the current AWS products and services. Since you have the data available on your system, you have the flexibility of creating your own version of Query API. Besides, the queries can be faster. On the downside, you have to maintain the up-to-date version of the offer file by yourself.

If you want to create your own system for AWS price list, you can refer to this [article](https://qiita.com/wayland/items/15f6b863f11c6b7c323c). It uses DynamoDB to store the downloaded data. Then queries can be done using DynamoDB commands. 


# Summary

While the command-line interface to the AWS price list is quick and easy to use, it can be limited when you want to create a pricing system of your own where your requirements are high. In this case, you can use create your own pricing system using the offer file provided by the Bulk API. However, you have to maintain an up-to-date offer file by yourself. AWS provides SNS service whenever their price changes. 

# References

[1. AWS Price List API](https://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/price-changes.html)

[2. jq](https://stedolan.github.io/jq/)