AWS Builder Center

Building CreditCLI with Kiro: from an AWS credit workflow to a safer cross-platform CLI

Learn how I built CreditCLI with Kiro to manage AWS promotional credits from the terminal—checking balances and expiration dates, exporting data, and registering codes through a guarded workflow. The article covers the GetCredits and RedeemCredits APIs, Boto3 and AWS Login authentication, secret-safe input, dry-run and confirmation gates, property-based testing, and Windows, macOS, and Linux packaging.

AWS Ambassador, AWS Authorized Instructor Champion, AWS Community Builder

Hello, Kiro Community!

I built CreditCLI with Kiro so that I could manage and operate AWS promotional credits without relying on the AWS Management Console for routine checks.
AWS promotional credits are useful, but I found them difficult to manage as an ongoing workflow.
To keep checking balances and expiration dates, I had to sign in to the AWS Management Console regularly, which became tedious.
The AWS CLI commands available in my environment did not provide this workflow, so I wanted a practical way to handle it from my terminal.
I also wanted to handle one related task in the same place.
If AWS provided an official way to register a promotional credit code, I wanted the tool to support that workflow without making a state-changing operation feel as casual as a balance check.
Those needs became CreditCLI, an open-source command-line tool for AWS promotional credits.
It provides balance, expiring, export, and redeem commands, runs from Python source, and is packaged for Windows, macOS, and Linux.
The source is available at https://github.com/hiro-sys/aws-credit-funcitons .

The three things I asked Kiro to help me achieve

I started with three outcomes, not API names or an implementation plan:
  1. I wanted to manage AWS promotional credits by checking balances and expiration dates.
  2. If AWS had an official method, I wanted to register a promotional credit code from the same tool.
  3. I wanted to export the data to CSV or another structured format for administration and follow-up processing.
I asked Kiro to investigate whether AWS provided official APIs for these workflows.
By searching the official AWS API Reference, we found GetCredits and RedeemCredits.
That discovery gave the project a clear boundary: GetCredits could support inspection, expiration checks, and CSV or JSON export, while RedeemCredits could support registration behind a deliberate safety gate.
From there, I worked with Kiro to turn those outcomes into requirements and design decisions.
We then expanded the work into Boto3 integration, authentication, safe input, testing, and distribution for three operating systems.
I did not hand the entire project over to Kiro.
Instead, I used Kiro as a development partner that helped carry my product and security decisions consistently through the code, tests, and release workflow.

What the official Billing APIs made possible

The GetCredits API  accepts a 12-digit accountId and requires a startDate in the past, no more than one year before the current time.
CreditCLI uses that response to build its balance, expiration, and export workflows.
The payerAccountFlag option also matters for consolidated billing.
When it is true and the caller is the management account, AWS can return credits for the consolidated billing family.
CreditCLI enables that behavior by default and provides --no-payer-account-flag when I want to query only the specified account.
The RedeemCredits API  accepts a promotional credit code from 1 through 256 characters.
A successful request returns HTTP 200 and registers the credit in the authenticated calling account's credit ledger.
Unlike GetCredits, the request does not contain a target Account ID; the authenticated calling account determines where the credit is registered.
That difference directly influenced the preflight checks and Account ID warnings described later.

The CLI I built

CreditCLI keeps the read-oriented commands small and composable:
CommandPurpose
balanceShow active credits, counts, and totals by currency. This is also the default when no subcommand is given.
expiringFind active credits expiring by the selected UTC cutoff.
exportWrite every credit returned by GetCredits as a table, JSON, or CSV.
redeemRegister promotional credit codes only after input validation, preflight, and an explicit safety decision.
Here are representative examples of all four commands:
1
2
3
4
./creditcli balance --profile EXAMPLE_PROFILE_NOT_REAL
./creditcli expiring --expiry-basis end-of-month --profile EXAMPLE_PROFILE_NOT_REAL
./creditcli export --format csv --output EXAMPLE_CREDITS_NOT_REAL.csv --profile EXAMPLE_PROFILE_NOT_REAL
./creditcli redeem --prompt-code --dry-run --profile EXAMPLE_PROFILE_NOT_REAL
balance shows credits that are enabled, still have a positive remaining amount, and have not expired.
Running CreditCLI without a subcommand also invokes balance.
expiring can use either the end of the current UTC month or the first instant of the next UTC month as its inclusive boundary.
export writes all credits returned by GetCredits in table, JSON, or CSV format.
The results can also be filtered by Account ID, credit type, or maximum remaining amount and sorted by expiration or remaining amount.
The --help output remains Japanese, even when English result output is available.
The following terminal output is the current top-level help; it includes the four subcommands and --version:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ ./creditcli --help
usage: creditcli [-h] [--version] {balance,expiring,redeem,export} ...

AWS プロモーショナルクレジットの残高照会・有効期限確認・登録・出力を行うクロスプラットフォーム CLI ツール

positional arguments:
{balance,expiring,redeem,export}
実行するサブコマンド(未指定時は balance として扱われる)
balance 現在有効なクレジット残高を表示する(既定動作)
expiring 基準日までに有効期限を迎えるクレジットを表示する
redeem promoCode を登録する
export クレジット情報を table/CSV/JSON 形式で出力する

options:
-h, --help show this help message and exit
--version バージョンを表示して終了する
Japanese is the default for user-facing results from balance, expiring, export, and redeem.
The --lang en option changes result labels, summaries, notifications, and errors to English.
It does not translate command help, so --help remains Japanese.
Here is one sanitized English balance example.
All IDs and identifying description text are masked, and the seven remaining amounts add up to the displayed USD total.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ ./creditcli balance --lang en
Credit ID | Description | Initial Amount | Remaining Amount | Currency | Start Date | End Date | Status | Account ID
----------+--------------------------+----------------+------------------+----------+----------------------+----------------------+---------+-------------
****-01 | **** promotional credit | 500.000000 | 500.000000 | USD | 2026-01-01T00:00:00Z | 2026-12-31T23:59:59Z | ENABLED | ************
****-02 | **** promotional credit | 500.000000 | 500.000000 | USD | 2026-01-01T00:00:00Z | 2026-12-31T23:59:59Z | ENABLED | ************
****-03 | **** promotional credit | 500.000000 | 500.000000 | USD | 2026-01-01T00:00:00Z | 2026-12-31T23:59:59Z | ENABLED | ************
****-04 | **** promotional credit | 250.000000 | 250.000000 | USD | 2026-02-01T00:00:00Z | 2027-01-31T23:59:59Z | ENABLED | ************
****-05 | **** promotional credit | 250.000000 | 250.000000 | USD | 2026-02-01T00:00:00Z | 2027-01-31T23:59:59Z | ENABLED | ************
****-06 | **** promotional credit | 200.000000 | 200.000000 | USD | 2026-03-01T00:00:00Z | 2027-02-28T23:59:59Z | ENABLED | ************
****-07 | **** promotional credit | 100.000000 | 50.010000 | USD | 2026-03-01T00:00:00Z | 2027-02-28T23:59:59Z | ENABLED | ************

Matching credits: 7
Totals by currency:
USD: 2,250.010000
The same language option applies to expiring and export user-facing output as well as redemption messages.
The help itself remains Japanese by design.
For completeness, this is a synthetic and sanitized example of a successful registration.
The Account ID is non-real, the hidden value is never shown, and no real code, fragment, or hash appears in the transcript.
1
2
3
4
5
6
7
8
$ ./creditcli redeem --prompt-code --lang en
Enter a promoCode (input hidden; leave empty to finish): [hidden input]
Enter a promoCode (input hidden; leave empty to finish): [empty input]
Target AWS Account ID: 000000000000
Redemption target count: 1
Proceed with redemption? [y/N]: y
Successful redemptions: 1
Failed redemptions: 0

Architecture and authentication

The implementation separates command behavior from AWS authentication and the Billing transport:
1
2
3
4
5
6
7
8
9
10
flowchart LR
CLI[CLI and input validation] --\> Auth[Boto3 credential resolution]
Auth --\> Select{Native Billing operations available}
Select --\>|Yes| Native[Boto3 Billing client]
Select --\>|No| Fallback[SigV4 HTTP fallback]
Native --\> Billing[AWS Billing in us-east-1]
Fallback --\> Billing
CLI --\> Output[Table, JSON, or CSV]
CLI --\> Gate[Redemption preflight and confirmation]
Gate --\> Select
CreditCLI uses the standard Boto3 credential provider chain.
If I omit --account-id, it calls STS GetCallerIdentity in us-east-1 to resolve the calling Account ID.
The relevant IAM actions are billing:GetCredits, billing:RedeemCredits, and, when caller identity must be resolved, sts:GetCallerIdentity.
I also wanted the packaged CLI to work with AWS Login credentials .
After aws login obtains short-term credentials, Boto3 can use the resulting profile through its Login credentials provider.
That provider requires AWS Common Runtime, so the runtime dependencies are pinned to these versions:
  • boto3==1.43.42
  • botocore[crt]==1.43.42
  • awscrt==0.32.2
For Billing calls, CreditCLI prefers the native Boto3 get_credits() and redeem_credits() operations available in the pinned environment.
It retains a SigV4-signed HTTP fallback for environments whose service model does not expose both operations.
The fallback is a compatibility boundary, not the source of the API design; the official AWS API Reference remains the contract I used to shape the workflows.

Making promotional credit code registration deliberately harder to trigger

Reading a balance and registering a credit have different risk profiles.
A failed read can be repeated, but an uncertain state-changing request may already have succeeded.
I therefore designed redeem as a guarded workflow rather than another direct subcommand call.
An unredeemed promotional credit code should be treated as a secret.
Putting it directly in a positional command argument can expose it through shell history or a process listing.
A plaintext file can also be copied into backups, synchronization services, editor history, or version control.
CreditCLI therefore recommends these two input modes instead:
1
2
./creditcli redeem --prompt-code --dry-run --profile EXAMPLE_PROFILE_NOT_REAL
SECRET_MANAGER_COMMAND_NOT_REAL | ./creditcli redeem --from-stdin --dry-run --profile EXAMPLE_PROFILE_NOT_REAL
--prompt-code reads hidden input from a TTY for a person at a terminal.
--from-stdin reads one UTF-8 code per line for approved automation or a secret provider and is always treated as non-interactive.
The positional and --from-file modes remain for compatibility, but safe and legacy input modes cannot be mixed.
Before any call to RedeemCredits, CreditCLI trims surrounding whitespace, drops empty entries, keeps only the first occurrence of a duplicate, and checks the 1-to-256-character limit.
It then shows only the resolved Account ID and the number of valid unique targets.
Individual results use one-based input numbers instead of a promotional credit code, fragment, or derived hash.
The execution gate follows a small set of rules:
  • --dry-run never constructs the Billing client or calls RedeemCredits.
  • An interactive run proceeds only when the response is y or yes, ignoring case and surrounding whitespace.
  • Any other response or EOF cancels safely with exit code 0.
  • A non-interactive real run requires --yes.
  • If --yes and --dry-run are supplied together, the dry run wins.
A dry run still resolves Boto3 credentials.
If --account-id is omitted, it also calls STS, so it is not necessarily offline.
It does not validate billing:RedeemCredits permission, determine whether a promotional credit code is registered or unused, verify that the code is valid, or predict whether AWS will accept it.
Here is a synthetic dry-run transcript using a non-real Account ID.
The input is hidden and then terminated with an empty entry; the value itself is not shown.
1
2
3
4
5
6
7
$ ./creditcli redeem --prompt-code --dry-run --lang en
Enter a promoCode (input hidden; leave empty to finish): [hidden input]
Enter a promoCode (input hidden; leave empty to finish): [empty input]
Target AWS Account ID: 000000000000
Redemption target count: 1
Static executability: executable (based only on input validation and Account ID resolution; AWS permissions and code usability are not checked)
Dry run: no redemption was performed.
This dry run is only a synthetic example of static input and Account ID checks.
It does not confirm AWS permissions, whether the code is registered or unused, or whether the code is valid and acceptable to AWS.
There is also an Account ID boundary to keep in mind.
RedeemCredits always applies to the authenticated calling account.
An explicitly supplied --account-id is checked for a 12-digit format but is not independently compared with the caller, so omitting it provides the stronger identity check through STS before a real registration.
Finally, the retry policies differ by operation.
GetCredits retries bounded transient failures, but RedeemCredits retries only throttling.
CreditCLI does not automatically repeat a network error, timeout, or HTTP 5xx for registration because the first request may already have changed the account.

Testing without real credentials or credits

The current suite has 747 passing tests across unit, integration, smoke, regression, and property-based layers.
Each safety-focused Property-Based Test runs 200 generated examples.
These properties cover input normalization, allowed input-mode combinations, confirmation decisions, API non-invocation, output independence from the contents of promotional credit codes, sanitization, ordered registration, and retry behavior.
The automated tests do not depend on real AWS credentials or a real promotional credit code.
They replace inherited AWS configuration with isolated synthetic settings, disable instance metadata, direct SDK endpoints to loopback, and block socket connections.
Synthetic clients and values exercise authentication initialization, API selection, preflight, confirmation, sanitization, and failure handling without sending requests to AWS public endpoints.
The public GitHub Actions workflow validates the release on all three supported operating systems:
RunnerResult
windows-latestPassed
macos-latestPassed
ubuntu-latestPassed
All three operating systems passed 747 tests, the PyInstaller distribution build, package smoke checks, and archive validation.
The public workflow results are available in GitHub Actions . 
PyInstaller cannot cross-compile these distributions, so GitHub Actions builds each operating system independently.
Every job verifies the pinned dependencies and runs the full suite.
It then builds a PyInstaller onedir distribution, a format that places the executable and its required libraries in one directory.
The job also checks _awscrt and the required botocore service models, then performs a synthetic AWS Login initialization smoke test before publishing an artifact.
I chose onedir because the executable must stay beside its Python modules, data, service models, and native libraries.
The GitHub Actions download contains an outer artifact ZIP and an inner distribution ZIP, so extraction takes two steps.
The macOS artifact is not Developer ID signed or notarized.
The Linux artifact is built on ubuntu-latest; AWS CloudShell has reported GLIBC_2.38 not found, so the current binary requires glibc 2.38 or later, source execution, or a rebuild for the target environment.
The Windows artifact and successful CI jobs also do not imply compatibility with every possible machine.
These packaging constraints are operational details rather than a claim of universal compatibility.

Trying CreditCLI

Python 3.10 or later is required when running from source:
1
2
3
4
5
git clone https://github.com/hiro-sys/aws-credit-funcitons.git
cd aws-credit-funcitons
python3 -m venv .venv
source .venv/bin/activate
python -m pip install .
I recommend starting with AWS Login and a read operation:
1
2
3
4
5
aws login --profile EXAMPLE_PROFILE_NOT_REAL
creditcli --help
creditcli balance --profile EXAMPLE_PROFILE_NOT_REAL
creditcli expiring --expiry-basis end-of-month --profile EXAMPLE_PROFILE_NOT_REAL
creditcli export --format csv --output EXAMPLE_CREDITS_NOT_REAL.csv --profile EXAMPLE_PROFILE_NOT_REAL
For registration, I first review a dry run with hidden input:
1
creditcli redeem --prompt-code --dry-run --profile EXAMPLE_PROFILE_NOT_REAL
Before removing --dry-run, I check the resolved Account ID, target count, IAM permissions, and the complete safety guidance in the repository.
I never place a real promotional credit code in an article command, screenshot command, issue, or log.

What I learned

Starting from the workflow kept the project focused.
I could ask Kiro to investigate the official AWS surface before committing to a transport or SDK method, and the GetCredits and RedeemCredits contracts then shaped separate read and state-changing paths.
I also learned that a useful CLI is more than an API wrapper.
For redeem, the design work was in the boundaries around the request: secret input, normalization, caller identity, preflight, explicit approval, conservative retries, and sanitized results.
For distribution, support for AWS Login was not complete until the CRT dependency and native extension were present and tested in each packaged operating system.
Kiro was most useful as a development partner for carrying those decisions across requirements, implementation, tests, and release checks.
The result still reflects choices I made about what the tool should do and what it should refuse to do automatically.

Limitations, license, and official references

CreditCLI is an independent, unofficial open-source project and is not an AWS service or endorsed by AWS.
It is distributed under the MIT License without warranty.
Before using it operationally, review the source and verify IAM permissions, the authenticated account, dependency compatibility, and network behavior in the target environment.
The current limitations include the unsigned and unnotarized macOS artifact, the Linux glibc requirement, Japanese-only command help, and the limited scope of dry-run described above.
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article