Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions lambda-invoicing-bedrock-cdk/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
node_modules/
build/
cdk.out/
cdk.context.json
*.js
*.d.ts
101 changes: 101 additions & 0 deletions lambda-invoicing-bedrock-cdk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Automated AWS Invoice Retrieval and Analysis with Amazon Bedrock

This pattern deploys an AWS Lambda function that automatically retrieves AWS invoices using the new programmatic Invoicing APIs, archives invoice PDFs to Amazon S3, and generates cost analysis summaries using Amazon Bedrock.

Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/lambda-invoicing-bedrock-cdk

Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. You are responsible for any AWS costs incurred. No warranty is implied in this example.

## Requirements

* [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources.
* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured
* [Node.js 20+](https://nodejs.org/en/download/) installed
* [AWS CDK v2](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html) installed (`npm install -g aws-cdk`)
* [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) installed

## Deployment Instructions

1. Create a new directory, navigate to that directory in a terminal and clone the GitHub repository:
```bash
git clone https://github.com/aws-samples/serverless-patterns
```
2. Change directory to the pattern directory:
```bash
cd serverless-patterns/lambda-invoicing-bedrock-cdk
```
3. Install dependencies:
```bash
npm install
```
4. Bootstrap CDK (if not already done):
```bash
cdk bootstrap
```
5. Deploy the stack:
```bash
cdk deploy
```

## How it works

This pattern uses four AWS services in composition:

1. **Amazon EventBridge** triggers the Lambda function on the 2nd of each month (after invoices become available).
2. **AWS Lambda** orchestrates the workflow: calls the AWS Invoicing API to list invoices and download PDFs.
3. **Amazon S3** stores the invoice PDFs with date-partitioned keys and lifecycle transitions to Infrequent Access after 90 days.
4. **Amazon Bedrock** (Claude Sonnet) analyzes the invoice data and generates an executive summary with spend breakdown, tax analysis, and cost optimization recommendations.

The pattern leverages the new `ListInvoiceSummaries` and `GetInvoicePDF` APIs (launched June 2026) to programmatically access invoices that previously required manual console downloads.

### Architecture

```
Amazon EventBridge (monthly) → AWS Lambda → Invoicing APIs → Amazon S3 (PDF archive)
→ Amazon Bedrock (analysis) → Amazon S3 (summary)
```

## Testing

Invoke the AWS Lambda function manually:

```bash
aws lambda invoke \
--function-name $(aws cloudformation describe-stacks \
--stack-name LambdaInvoicingBedrockStack \
--query 'Stacks[0].Outputs[?OutputKey==`FunctionName`].OutputValue' \
--output text) \
--payload '{}' \
response.json && cat response.json
```

Expected output shows invoices retrieved, PDFs archived, and Bedrock analysis:

```json
{
"statusCode": 200,
"body": "{\"message\": \"Processed 1 invoice(s)\", \"period\": \"2026-05\", \"totalInvoices\": 1, \"analysisKey\": \"analysis/2026/05/summary.json\", \"analysis\": \"...\"}"
}
```

Check the Amazon S3 bucket for archived PDFs and analysis:

```bash
aws s3 ls s3://$(aws cloudformation describe-stacks \
--stack-name LambdaInvoicingBedrockStack \
--query 'Stacks[0].Outputs[?OutputKey==`InvoiceBucketName`].OutputValue' \
--output text) --recursive
```

## Cleanup

> **Warning:** Running `cdk destroy` will delete the Amazon S3 bucket and all archived invoice PDFs. Download any needed invoices before destroying.

```bash
cdk destroy
```

----
Copyright 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved.

SPDX-License-Identifier: MIT-0
12 changes: 12 additions & 0 deletions lambda-invoicing-bedrock-cdk/bin/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { LambdaInvoicingBedrockStack } from '../lib/lambda-invoicing-bedrock-stack';

const app = new cdk.App();
new LambdaInvoicingBedrockStack(app, 'LambdaInvoicingBedrockStack', {
env: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION,
},
});
7 changes: 7 additions & 0 deletions lambda-invoicing-bedrock-cdk/cdk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"app": "npx ts-node --prefer-ts-exts bin/app.ts",
"watch": {
"include": ["**"],
"exclude": ["node_modules", "build", "cdk.out"]
}
}
101 changes: 101 additions & 0 deletions lambda-invoicing-bedrock-cdk/example-pattern.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
{
"title": "Automated AWS Invoice Retrieval and Analysis with Amazon Bedrock",
"description": "Retrieve AWS invoices programmatically, archive PDFs to Amazon S3, and generate cost analysis summaries using Amazon Bedrock.",
"language": "Python",
"level": "200",
"framework": "AWS CDK",
"introBox": {
"headline": "How it works",
"text": [
"An Amazon EventBridge scheduled rule triggers an AWS Lambda function on the 2nd of each month.",
"The AWS Lambda function calls the AWS Invoicing API to list invoice summaries and download invoice PDFs via presigned URLs.",
"Invoice PDFs are archived to Amazon S3 with date-partitioned prefixes and lifecycle transitions to Infrequent Access.",
"Amazon Bedrock analyzes the invoice data and generates an executive summary with cost optimization recommendations.",
"The analysis results are stored alongside the PDFs in Amazon S3 for historical tracking and audit compliance."
]
},
"gitHub": {
"template": {
"repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/lambda-invoicing-bedrock-cdk",
"templateURL": "serverless-patterns/lambda-invoicing-bedrock-cdk",
"projectFolder": "lambda-invoicing-bedrock-cdk",
"templateFile": "lib/lambda-invoicing-bedrock-stack.ts"
}
},
"resources": {
"bullets": [
{
"text": "Automate AWS Invoice Retrieval with New Programmatic APIs",
"link": "https://aws.amazon.com/blogs/aws-cloud-financial-management/automate-aws-invoice-retrieval-with-new-programmatic-apis/"
},
{
"text": "AWS Invoicing API Reference",
"link": "https://docs.aws.amazon.com/aws-cost-management/latest/APIReference/API_invoicing_ListInvoiceSummaries.html"
}
]
},
"deploy": {
"text": [
"<code>cdk bootstrap</code>",
"<code>npm install</code>",
"<code>cdk deploy</code>"
]
},
"testing": {
"text": [
"Invoke the AWS Lambda function manually to test invoice retrieval:",
"<code>aws lambda invoke --function-name $(aws cloudformation describe-stacks --stack-name LambdaInvoicingBedrockStack --query 'Stacks[0].Outputs[?OutputKey==`FunctionName`].OutputValue' --output text) --payload '{}' response.json && cat response.json</code>"
]
},
"cleanup": {
"text": [
"Warning: This will delete the Amazon S3 bucket and all archived invoices.",
"<code>cdk destroy</code>"
]
},
"authors": [
{
"name": "Nithin Chandran R",
"bio": "Technical Account Manager at AWS",
"linkedin": "nithin-chandran-r"
}
],
"patternArch": {
"icon1": {
"x": 10,
"y": 50,
"service": "eventbridge",
"label": "Amazon EventBridge"
},
"icon2": {
"x": 35,
"y": 50,
"service": "lambda",
"label": "AWS Lambda"
},
"icon3": {
"x": 60,
"y": 30,
"service": "s3",
"label": "Amazon S3"
},
"icon4": {
"x": 60,
"y": 70,
"service": "bedrock",
"label": "Amazon Bedrock"
},
"line1": {
"from": "icon1",
"to": "icon2"
},
"line2": {
"from": "icon2",
"to": "icon3"
},
"line3": {
"from": "icon2",
"to": "icon4"
}
}
}
82 changes: 82 additions & 0 deletions lambda-invoicing-bedrock-cdk/lib/lambda-invoicing-bedrock-stack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as events from 'aws-cdk-lib/aws-events';
import * as targets from 'aws-cdk-lib/aws-events-targets';
import * as path from 'path';

export class LambdaInvoicingBedrockStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);

// Amazon S3 bucket for invoice PDF archive
const invoiceBucket = new s3.Bucket(this, 'InvoiceBucket', {
removalPolicy: cdk.RemovalPolicy.DESTROY,
autoDeleteObjects: true,
encryption: s3.BucketEncryption.S3_MANAGED,
lifecycleRules: [
{
transitions: [
{
storageClass: s3.StorageClass.INFREQUENT_ACCESS,
transitionAfter: cdk.Duration.days(90),
},
],
},
],
});

// AWS Lambda function for invoice retrieval and Amazon Bedrock analysis
const invoiceFn = new lambda.Function(this, 'InvoiceFunction', {
runtime: lambda.Runtime.PYTHON_3_12,
handler: 'index.handler',
code: lambda.Code.fromAsset(path.join(__dirname, '..', 'src')),
timeout: cdk.Duration.minutes(5),
memorySize: 512,
environment: {
BUCKET_NAME: invoiceBucket.bucketName,
MODEL_ID: 'us.anthropic.claude-sonnet-4-20250514-v1:0',
},
});

// Grant Amazon S3 write access
invoiceBucket.grantWrite(invoiceFn);

// Grant Invoicing API access
invoiceFn.addToRolePolicy(new iam.PolicyStatement({
actions: [
'invoicing:ListInvoiceSummaries',
'invoicing:GetInvoicePDF',
],
resources: ['*'],
}));

// Grant Bedrock model invocation
invoiceFn.addToRolePolicy(new iam.PolicyStatement({
actions: ['bedrock:InvokeModel'],
resources: [
`arn:aws:bedrock:*::foundation-model/anthropic.claude-sonnet-4-20250514-v1:0`,
`arn:aws:bedrock:${this.region}:${this.account}:inference-profile/us.anthropic.claude-sonnet-4-20250514-v1:0`,
],
}));

// Amazon EventBridge rule - runs on the 2nd of each month (invoices available after month-end)
const rule = new events.Rule(this, 'MonthlyInvoiceRule', {
schedule: events.Schedule.cron({ minute: '0', hour: '8', day: '2', month: '*' }),
});
rule.addTarget(new targets.LambdaFunction(invoiceFn));

// Outputs
new cdk.CfnOutput(this, 'InvoiceBucketName', {
value: invoiceBucket.bucketName,
description: 'Amazon S3 bucket storing invoice PDFs and Amazon Bedrock analysis summaries',
});

new cdk.CfnOutput(this, 'FunctionName', {
value: invoiceFn.functionName,
description: 'AWS Lambda function name for manual invocation',
});
}
}
21 changes: 21 additions & 0 deletions lambda-invoicing-bedrock-cdk/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "lambda-invoicing-bedrock-cdk",
"version": "1.0.0",
"bin": {
"app": "bin/app.ts"
},
"scripts": {
"build": "tsc",
"cdk": "cdk"
},
"dependencies": {
"aws-cdk-lib": "^2.170.0",
"constructs": "^10.0.0",
"source-map-support": "^0.5.21"
},
"devDependencies": {
"@types/node": "^20.0.0",
"typescript": "~5.6.0",
"aws-cdk": "^2.170.0"
}
}
Loading