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.
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.
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.
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
The source is available at https://github.com/hiro-sys/aws-credit-funcitons .
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:
- I wanted to manage AWS promotional credits by checking balances and expiration dates.
- If AWS had an official method, I wanted to register a promotional credit code from the same tool.
- 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
That discovery gave the project a clear boundary:
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.
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
CreditCLI uses that response to build its balance, expiration, and export workflows.
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
When it is
CreditCLI enables that behavior by default and provides
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
A successful request returns HTTP 200 and registers the credit in the authenticated calling account's credit ledger.
Unlike
That difference directly influenced the preflight checks and Account ID warnings described later.
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:
| Command | Purpose |
|---|---|
balance | Show active credits, counts, and totals by currency. This is also the default when no subcommand is given. |
expiring | Find active credits expiring by the selected UTC cutoff. |
export | Write every credit returned by GetCredits as a table, JSON, or CSV. |
redeem | Register 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_REALbalance 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
The following terminal output is the current top-level help; it includes the four subcommands and
--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
The
It does not translate command help, so
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.
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.010000The same language option applies to
The help itself remains Japanese by design.
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.
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: 0Architecture 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 --\> SelectCreditCLI uses the standard Boto3 credential provider chain.
If I omit
The relevant IAM actions are
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
That provider requires AWS Common Runtime, so the runtime dependencies are pinned to these versions:
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.42botocore[crt]==1.43.42awscrt==0.32.2
For Billing calls, CreditCLI prefers the native Boto3
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.
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
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:
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
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.
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-runnever constructs the Billing client or callsRedeemCredits.- An interactive run proceeds only when the response is
yoryes, ignoring case and surrounding whitespace. - Any other response or EOF cancels safely with exit code 0.
- A non-interactive real run requires
--yes. - If
--yesand--dry-runare supplied together, the dry run wins.
A dry run still resolves Boto3 credentials.
If
It does not validate
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.
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.
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.
An explicitly supplied
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.
CreditCLI does not automatically repeat a network error, timeout, or HTTP 5xx for registration because the first request may already have changed the account.
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.
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.
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:
| Runner | Result |
|---|---|
windows-latest | Passed |
macos-latest | Passed |
ubuntu-latest | Passed |
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 .
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
The job also checks
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
The GitHub Actions download contains an outer artifact ZIP and an inner distribution ZIP, so extraction takes two steps.
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
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.
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_REALFor registration, I first review a dry run with hidden input:
1
creditcli redeem --prompt-code --dry-run --profile EXAMPLE_PROFILE_NOT_REALBefore removing
I never place a real promotional credit code in an article command, screenshot command, issue, or log.
--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
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
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.
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.
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.
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.
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article