Serverless AI Writing Assistant API

Build a REST API that generates AI content using Lambda and Bedrock.

Introduction

30 Second Summary

Every AI writing tool you use works the same way behind the scenes. Your app sends a request to an API, the API calls an AI model, and generated text comes back.

In this project, you will build that entire pipeline yourself on AWS. You will create a serverless REST API that accepts any topic and returns an AI-generated article, with every resource deployed through CLI commands.

What You'll Build

You will fire off a curl command from your terminal with any topic and watch your API return a freshly written article, powered by Amazon Bedrock's Nova Lite model.

By the end of this project, you'll have:

  • A live API endpoint you can call with curl from anywhere, passing in any topic and getting an AI-written article back in seconds.
  • A Lambda function that wraps the Bedrock Converse API, handling prompt construction and response parsing behind the scenes.
  • A public API Gateway endpoint that exposes your AI assistant to the internet as a POST /generate route, deployed entirely from the command line.
  • Secret Mission: Add AI writing personas (pirate, professor, poet) that dynamically change the model's writing style based on a request parameter.

Are there any prerequisites?

You need an AWS account, the AWS CLI, and Python 3 installed on your machine. Step 1 includes setup options for each tool if you need to install or configure any of them.

Before We Start

Before you start building, take a moment to lock in what you're about to create and why it matters.

Set Up Your Tools and AWS Environment

Before you can build anything on AWS, you need the right tools on your machine and the right permissions in the cloud.

In this step, you will install the AWS CLI, configure your credentials, and enable access to Amazon Bedrock's Nova Lite model so that every later step can talk to AWS without friction.

In this step, get ready to:
  • Install and verify the AWS CLI and Python.
  • Configure your AWS credentials with aws configure.
  • Enable Amazon Nova Lite model access in the Bedrock console.
Install the AWS CLI and Python

The AWS CLI is the command-line tool you will use to create every AWS resource in this project. Python is needed because the Lambda function you build later is written in Python.

  • Check whether the AWS CLI is already installed by running this command:
aws --version

✔️ I see a version number

The AWS CLI is ready. Move on to the Python check below.

ⓧ Command not found

  • Install the AWS CLI by running this command:
curl -fsSL https://awscli.amazonaws.com/v2/install.sh | bash

What does this command do?

This downloads the official AWS install script and runs it in one step. It works on both x86 and ARM Linux systems and installs the CLI for your current user.

  • Close and reopen your terminal, then verify the installation by running:
aws --version

You should see a version string starting with aws-cli/2.

Still seeing command not found?

Make sure you opened a brand-new terminal window after the install. The CLI binary might not be on your PATH in the old session.

If the issue persists, try the manual install method from the AWS CLI install guide.

Help me troubleshoot AWS CLI installation on Linux

  • Now check whether Python 3 is installed by running:
python3 --version

✔️ I see a version number

Python is ready. Move on to configuring your AWS credentials.

ⓧ Command not found

Most Linux distributions ship with Python 3 pre-installed. If yours does not, install it with your package manager.

  • On Ubuntu or Debian, install Python 3 by running:
sudo apt update && sudo apt install python3 -y
  • On Fedora or Amazon Linux, install Python 3 by running:
sudo dnf install python3 -y
  • Verify by running:
python3 --version

You should see a version number printed to the terminal.

Still can't find Python?

Help me install Python 3 on my Linux distribution

Configure your AWS credentials

The AWS CLI needs credentials so it can act on your behalf when creating resources. You will connect it to your AWS account using an Access Key ID and Secret Access Key.

✔️ I already have an Access Key ID and Secret Access Key

You are all set. Continue to the aws configure step below.

ⓧ I need to create an AWS account or access keys

  • If you do not have an AWS account yet, create one at aws.amazon.com/free.
  • Sign in to the AWS Management Console.
  • Search for IAM in the top search bar and open the IAM service.
  • Click Users in the left sidebar.
  • Click Create user.
  • Enter a username (e.g. cli-user) and click Next.
  • Select Attach policies directly and search for AdministratorAccess.
  • Check the box next to AdministratorAccess and click Next.
  • Click Create user.
  • Click on the new user's name to open their details.
  • Click the Security credentials tab.
  • Click Create access key.
  • Select Command Line Interface (CLI) as the use case.
  • Check the confirmation checkbox and click Next.
  • Click Create access key.
  • Copy your Access Key ID and Secret Access Key somewhere safe. You will not be able to see the Secret Access Key again after leaving this page.

Keep your keys safe

Never share your Access Key ID or Secret Access Key publicly. Do not paste them into code files or commit them to version control.

  • Configure the AWS CLI with your credentials by running:
aws configure

The command will prompt you for four values. Enter them as follows:

  • For AWS Access Key ID: paste your Access Key ID.
  • For AWS Secret Access Key: paste your Secret Access Key.
  • For Default region name: enter us-east-1.
  • For Default output format: enter json.

Why us-east-1?

Amazon Nova Lite is available in the us-east-1 (N. Virginia) region. All the AWS resources you create in this project will live in that region to keep everything connected.

  • Confirm your configuration is working by running:
aws sts get-caller-identity

You should see a JSON response containing your Account number, UserId, and Arn. This confirms the CLI is authenticated.

Seeing an error about credentials?

Double-check that you pasted the full Access Key ID and Secret Access Key without any extra spaces. Run aws configure again to re-enter them.

Help me fix my AWS CLI credentials configuration

Enable Amazon Nova Lite model access

Amazon Bedrock requires you to explicitly enable each foundation model before you can call it. You will enable Amazon Nova Lite, which is the model powering your writing assistant.

  • Open the AWS Management Console in your browser and sign in.
  • Make sure you are in the US East (N. Virginia) region. Check the region name in the top-right corner of the console.
  • If it shows a different region, click the region name and select US East (N. Virginia).
  • Search for Bedrock in the top search bar and open Amazon Bedrock.
  • In the left navigation pane, under Bedrock configurations, click Model access.
  • Click Modify model access.
  • Find Amazon Nova Lite in the list and check the box next to it.
  • Click Next.
  • Review the changes. Read the Terms and click Submit.

What is Amazon Bedrock?

Amazon Bedrock is a fully managed service that gives you access to foundation models from Amazon and third-party providers through a single API. Instead of hosting your own AI model, you call Bedrock and pay only for what you use.

Nova Lite is one of Amazon's own models. It costs $0.06 per 1M input tokens and $0.24 per 1M output tokens, making it one of the cheapest options on Bedrock.

The access change can take a few minutes to process. Wait until the Access status column shows Access granted next to Amazon Nova Lite before continuing.

Before you run the verification command below, what do you think it will return if model access was granted correctly?

  • Verify that model access is working by running this command in your terminal:
aws bedrock list-foundation-models --query "modelSummaries[?modelId=='amazon.nova-lite-v1:0'].modelId" --output text --region us-east-1

You should see amazon.nova-lite-v1:0 printed to your terminal. This confirms your credentials, region, and model access are all wired correctly.

Seeing an empty result or access denied?

Go back to the Bedrock console and confirm the Access status for Nova Lite shows Access granted. The change can take several minutes to process.

Also confirm your CLI region is set to us-east-1 by running:

aws configure get region

Help me troubleshoot Bedrock model access

Your tools and AWS environment are ready. Next up, you will call the Nova Lite model directly from the CLI to see AI-generated content with your own eyes.

Call the AI Model from the CLI

Your AWS environment is set up and ready. You have the CLI configured, Python installed, and Amazon Nova Lite model access enabled.

Before building any infrastructure around the model, you want to see it respond with your own eyes. A direct CLI call proves that your credentials, permissions, and model access are all wired correctly.

In this step, get ready to:
  • Invoke Amazon Nova Lite directly from the CLI and examine the response structure.
  • Experiment with inference parameters to see how they change the model's output.
Invoke Amazon Nova Lite from the CLI

The Bedrock Converse API provides a unified way to call foundation models. You send it a model ID, a list of messages, and optional inference settings.

Before you run the command below, what do you expect the response to look like? Plain text, or structured data?

  • Invoke Amazon Nova Lite by running this command in your terminal:
aws bedrock-runtime converse \
  --model-id amazon.nova-lite-v1:0 \
  --messages '[{"role": "user", "content": [{"text": "Write a short paragraph about cloud computing"}]}]' \
  --inference-config '{"maxTokens": 512, "temperature": 0.5, "topP": 0.9}'

The response is a JSON object, not plain text. Your generated paragraph is nested inside the response at the path output.message.content[0].text.

What does this response contain?

The JSON response includes several fields beyond the generated text.

  • output.message.content[0].text holds the generated text itself.
  • stopReason tells you why the model stopped generating. A value of end_turn means the model finished naturally.
  • usage shows how many input and output tokens the call consumed.
  • Scroll through your JSON response and find the output.message.content[0].text field. You should see a paragraph about cloud computing.

Seeing an access denied error?

If you see AccessDeniedException: You don't have access to the model with the specified model ID, make sure you completed the model access request in Step 1. Return to the Amazon Bedrock console in the us-east-1 region and verify that Amazon Nova Lite shows Access granted.

If the command returns a different region error, verify your CLI default region by running:

aws configure get region

The output should be us-east-1. Help me debug my Bedrock converse command.

Experiment with inference parameters

Two key parameters control how the model generates its response. Changing them produces noticeably different results.

What are temperature and maxTokens?

temperature controls randomness. A value close to 0 produces predictable, focused text. A value close to 1 produces more creative, varied text.

maxTokens sets the maximum length of the generated response. A lower value forces the model to keep its answer short.

  • Run the same prompt again, but this time with temperature set to 0.9 instead of 0.5:
aws bedrock-runtime converse \
  --model-id amazon.nova-lite-v1:0 \
  --messages '[{"role": "user", "content": [{"text": "Write a short paragraph about cloud computing"}]}]' \
  --inference-config '{"maxTokens": 512, "temperature": 0.9, "topP": 0.9}'

Compare this response to your first one. With a higher temperature, the writing style is more varied and creative.

  • Now try a completely different prompt with a lower maxTokens value by running:
aws bedrock-runtime converse \
  --model-id amazon.nova-lite-v1:0 \
  --messages '[{"role": "user", "content": [{"text": "Write a haiku about the ocean"}]}]' \
  --inference-config '{"maxTokens": 100, "temperature": 0.7, "topP": 0.9}'

The response is noticeably shorter. The model stops generating once it reaches the maxTokens ceiling you set.

Pick the parameters you like

There is no single correct combination. In the next step, you'll bake these parameters into a Lambda function. The Complete Code uses temperature 0.7 and maxTokens 512 as a balanced default.

You've proven the model works and explored how to control its output. Next, you'll wrap this Bedrock call inside a Lambda function so other applications can trigger it as a service.

Build and Deploy the Lambda Function

In the last step, you called Amazon Nova Lite directly from the CLI and saw AI-generated text stream back in JSON. That proves your credentials and model access work.

A CLI command is great for testing, but other applications cannot call it. To turn your Bedrock call into a reusable service, you need to wrap it inside an AWS Lambda function. In this step, you will create an IAM role with least-privilege permissions, write the function code in Python, and deploy it to Lambda.

In this step, get ready to:
  • Create an IAM execution role with scoped Bedrock permissions.
  • Write a Lambda function that calls the Bedrock Converse API.
  • Deploy and test the function, hitting a real-world timeout issue.
Create the trust policy file

Before Lambda can use an IAM role, the role needs a trust policy that explicitly allows the Lambda service to assume it. You will write this policy as a JSON file.

  • Create a file called trust-policy.json by running this command:
cat > trust-policy.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF

What does this file do?

A trust policy defines who is allowed to assume a role. This policy says only the lambda.amazonaws.com service can assume this role.

Without it, Lambda would be denied permission to use the role, and your function would fail to start.

  • Confirm the file was created by running:
cat trust-policy.json

You should see the JSON trust policy printed to your terminal with the lambda.amazonaws.com principal.

Create the IAM role and attach permissions

Now you will use the trust policy file to create an IAM role, then attach two permission policies to it. One policy allows Lambda to write logs. The other grants access to call the Bedrock model.

  • Create the IAM role by running this command:
aws iam create-role \
  --role-name ai-writing-lambda-role \
  --assume-role-policy-document file://trust-policy.json

You should see a JSON response containing the new role's details, including an Arn field.

  • Copy the value of the Arn field and save it somewhere. You will need it when you deploy the Lambda function.

What is the role ARN?

An ARN (Amazon Resource Name) is a unique identifier for every resource in AWS. The role ARN looks something like arn:aws:iam::123456789012:role/ai-writing-lambda-role.

You will pass this ARN to Lambda so it knows which role to use when running your function.

Seeing an error that the role already exists?

This means a role named ai-writing-lambda-role already exists in your account from a previous attempt. You can continue to the next command safely.

Help me fix my IAM role creation error.

  • Attach the AWS managed AWSLambdaBasicExecutionRole policy for CloudWatch Logs by running:
aws iam attach-role-policy \
  --role-name ai-writing-lambda-role \
  --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

Why this managed policy?

AWSLambdaBasicExecutionRole grants three CloudWatch Logs permissions: logs:CreateLogGroup, logs:CreateLogStream, and logs:PutLogEvents. Without these, your Lambda function would run but you would never see its logs.

This command produces no output on success.

  • Create the Bedrock permissions file by running:
cat > bedrock-policy.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "bedrock:InvokeModel",
      "Resource": "arn:aws:bedrock:us-east-1::foundation-model/amazon.nova-lite-v1:0"
    }
  ]
}
EOF
  • Confirm the file was created by running:
cat bedrock-policy.json

You should see the JSON policy printed with a single bedrock:InvokeModel action scoped to the Nova Lite model.

What is least-privilege access?

This policy grants bedrock:InvokeModel on exactly one model: amazon.nova-lite-v1:0. The role cannot invoke any other model or perform any other Bedrock action.

This is called least-privilege access. You give the role only the permissions it absolutely needs. The bedrock:InvokeModel action also covers calls made through the Converse API.

  • Attach this as an inline policy named BedrockInvokeAccess by running:
aws iam put-role-policy \
  --role-name ai-writing-lambda-role \
  --policy-name BedrockInvokeAccess \
  --policy-document file://bedrock-policy.json

This command also produces no output on success.

Validate the role in the IAM Console

You created the role entirely from the CLI. Now confirm it exists visually in the AWS Console.

  • Open the AWS Console in your browser and navigate to the IAM service.
  • Click Roles in the left navigation.
  • Search for ai-writing-lambda-role in the search bar.
  • Click on the role name to open its details.
  • Under the Permissions tab, confirm you see two policies: AWSLambdaBasicExecutionRole and BedrockInvokeAccess.

Why check in the Console?

Validating CLI-created resources in the Console connects the commands you ran to the infrastructure they created. This is a key skill when managing AWS resources with infrastructure-as-code.

Write the Lambda function code

With the IAM role ready, you can write the function that will run inside Lambda. This function receives a topic, sends it to Amazon Nova Lite via the Converse API, and returns the AI-generated article as a JSON response.

  • Create the lambda_function.py file by running:
cat > lambda_function.py << 'EOF'
import json
import boto3

bedrock = boto3.client("bedrock-runtime", region_name="us-east-1")


def lambda_handler(event, context):
    body = json.loads(event.get("body", "{}"))
    topic = body.get("topic", "cloud computing")

    response = bedrock.converse(
        modelId="amazon.nova-lite-v1:0",
        messages=[
            {
                "role": "user",
                "content": [{"text": f"Write a short, engaging article about: {topic}"}],
            }
        ],
        inferenceConfig={"maxTokens": 512, "temperature": 0.7, "topP": 0.9},
    )

    result = response["output"]["message"]["content"][0]["text"]

    return {
        "statusCode": 200,
        "headers": {"Content-Type": "application/json"},
        "body": json.dumps({"topic": topic, "article": result}),
    }
EOF

What does this code do?

  • boto3.client("bedrock-runtime") creates a client to call Bedrock. Lambda's Python runtime includes boto3 by default, so you do not need to package it separately.
  • lambda_handler(event, context) is the entry point Lambda calls. It parses the incoming body JSON to extract a topic, defaulting to cloud computing if none is provided.
  • The converse() call sends the topic to Amazon Nova Lite, just like the CLI command you ran in Step 2.
  • The function returns a dictionary with statusCode, headers, and body. This format is required by API Gateway's Lambda proxy integration, which you will set up in the next step.
  • Confirm the file was created by running:
cat lambda_function.py

You should see the full Python function printed to your terminal, starting with the import json line.

File looks different from expected?

Make sure you copied the entire command block including the EOF at the end. Missing the closing EOF can cause the file to be incomplete or empty.

Help me fix my lambda_function.py file.

Package and deploy the Lambda function

Lambda expects your code as a zip file. Since boto3 is already included in the Lambda Python runtime, you only need to zip your single Python file.

  • Package the code into a zip file by running:
zip function.zip lambda_function.py

You should see output confirming lambda_function.py was added to the archive.

  • Deploy the Lambda function by running this command, replacing the --role value with the role ARN you copied earlier:
aws lambda create-function \
  --function-name ai-writing-assistant \
  --runtime python3.12 \
  --zip-file fileb://function.zip \
  --handler lambda_function.lambda_handler \
  --role [[ROLE_ARN="arn:aws:iam::123456789012:role/ai-writing-lambda-role"]]

What do these flags mean?

  • --function-name ai-writing-assistant names your function in AWS.
  • --runtime python3.12 tells Lambda which language runtime to use.
  • --handler lambda_function.lambda_handler tells Lambda the file name (lambda_function) and function name (lambda_handler) to invoke.
  • --zip-file fileb://function.zip uploads your zip file. The fileb:// prefix tells the CLI to read the file as binary.

You should see a JSON response with the function's configuration details, including "Timeout": 3 in the output. Keep that number in mind.

Seeing an error about the role parameter?

This usually means the role ARN is incorrect or the role has not fully propagated yet. IAM changes can take a few seconds to propagate across AWS.

Wait 10 seconds and try the command again. If it still fails, double-check that the ARN matches the one from the create-role output. Help me fix my Lambda deployment error.

Test the Lambda function and observe the timeout

Your function is deployed. Before you connect it to anything else, test it directly using the aws lambda invoke command.

Before you run the test, think about this: your Bedrock CLI call in Step 2 took several seconds to return. Do you think the Lambda function will succeed with a 3-second timeout?

  • Test the function by running:
aws lambda invoke \
  --function-name ai-writing-assistant \
  --cli-binary-format raw-in-base64-out \
  --payload '{"body": "{\"topic\": \"artificial intelligence\"}" }' \
  output.json
  • Check what was returned by running:
cat output.json

You will see a timeout error. The function did not return a successful result.

This timeout error is expected

Lambda defaults to a 3-second timeout. You saw "Timeout": 3 in the create-function output earlier. Bedrock model invocations typically take 5 to 15 seconds, so the function is being killed before Bedrock can respond.

This is a real-world issue that catches many beginners. You will fix it in the next step.

✔️ Awesome, I've got everything!

Great. Double check you have trust-policy.json, bedrock-policy.json, and lambda_function.py in your working directory.

ⓧ I'd like to double check the full code

Here is the trust policy file you should have:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

Here is the Bedrock permissions file:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "bedrock:InvokeModel",
      "Resource": "arn:aws:bedrock:us-east-1::foundation-model/amazon.nova-lite-v1:0"
    }
  ]
}

Here is the Lambda function code:

import json
import boto3

bedrock = boto3.client("bedrock-runtime", region_name="us-east-1")


def lambda_handler(event, context):
    body = json.loads(event.get("body", "{}"))
    topic = body.get("topic", "cloud computing")

    response = bedrock.converse(
        modelId="amazon.nova-lite-v1:0",
        messages=[
            {
                "role": "user",
                "content": [{"text": f"Write a short, engaging article about: {topic}"}],
            }
        ],
        inferenceConfig={"maxTokens": 512, "temperature": 0.7, "topP": 0.9},
    )

    result = response["output"]["message"]["content"][0]["text"]

    return {
        "statusCode": 200,
        "headers": {"Content-Type": "application/json"},
        "body": json.dumps({"topic": topic, "article": result}),
    }

Your Lambda function is deployed and you have confirmed the timeout issue firsthand. In the next step, you will fix the timeout and wire the function up to a public API endpoint.

Fix the Timeout and Expose via API Gateway

In the last step, you deployed your Lambda function and tested it. The result was a timeout error. That is the gap you are closing now.

Lambda defaults to a 3-second timeout, but Amazon Bedrock model calls typically take 5 to 15 seconds to respond. After you fix the timeout, you still need a way for the outside world to reach your function. That is where Amazon API Gateway comes in. It gives your Lambda function a public HTTP endpoint that any application can call.

In this step, get ready to:
  • Fix the Lambda timeout and confirm the function returns AI-generated content.
  • Create a REST API with API Gateway and wire it to your Lambda function.
  • Test the live API endpoint with a curl request.
Fix the Lambda timeout

Your Lambda function timed out because the default is only 3 seconds. Bedrock needs more time than that to generate a response.

  • Update the timeout to 30 seconds by running this command:
aws lambda update-function-configuration \
  --function-name ai-writing-assistant \
  --timeout 30

What does this command do?

The --timeout 30 flag sets the maximum execution time to 30 seconds. Lambda allows values from 1 to 900 seconds (15 minutes). 30 seconds gives Bedrock plenty of room to respond without being wasteful.

You should see a JSON response that includes "Timeout": 30 in the output, confirming the change.

Now re-test the function. Before you run it, think about what you expect to happen this time. Do you think the function will succeed or time out again?

  • Invoke your function again by running this command:
aws lambda invoke \
  --function-name ai-writing-assistant \
  --cli-binary-format raw-in-base64-out \
  --payload '{"body": "{\\\"topic\\\": \\\"artificial intelligence\\\"}"}' \
  response.json
  • View the response by running:
cat response.json

This time, you should see a full JSON response containing a statusCode of 200 and an article field with AI-generated content about artificial intelligence.

Still seeing a timeout error?

Make sure the update-function-configuration command finished before you ran invoke. Lambda configuration updates can take a few seconds to propagate. Wait 10 seconds and try again.

If you see an access denied error, double-check that the BedrockInvokeAccess inline policy is attached to your ai-writing-lambda-role role in the IAM Console.

Help me debug my Lambda timeout issue.

Validate in the Lambda Console
  • Open the AWS Console in your browser and navigate to the Lambda service.
  • Click on ai-writing-assistant in the functions list.
  • Click the Configuration tab, then select General configuration.
  • Confirm the Timeout field shows 0 min 30 sec.

Why validate in the Console?

You deployed everything through the CLI. Checking the Console confirms the CLI commands actually created the resources you intended. This is a core skill in infrastructure-as-code: deploy via CLI, verify visually.

Create the API Gateway REST API

Your Lambda function works, but only you can call it from the CLI. To make it accessible over HTTP, you need an API Gateway endpoint in front of it.

Setting up a REST API in API Gateway involves several connected steps. You will create the API, add a resource path, attach a method, wire up the Lambda integration, grant permissions, and deploy to a stage.

  • Create the REST API by running this command:
aws apigateway create-rest-api \
  --name 'AI-Writing-API' \
  --region us-east-1

What does this command do?

This creates a new REST API named AI-Writing-API in API Gateway. The output includes an id field. This is your API ID. You will use it in every command that follows.

The output also includes a rootResourceId field. This is the ID of the root / path. You will use it to create your /generate resource underneath it.

  • Copy the id value from the output. This is your API ID.
  • Copy the rootResourceId value from the output. This is your root resource ID.
  • Create the /generate resource by running this command, replacing the placeholder values with your actual IDs:
aws apigateway create-resource \
  --rest-api-id [[API_ID="your API ID from the previous command"]] \
  --parent-id [[ROOT_RESOURCE_ID="your root resource ID"]] \
  --path-part generate
  • Copy the id from the output. This is your resource ID for the /generate path.

You now have an API with a /generate path. Next, add a POST method to it.

  • Add a POST method to the /generate resource by running:
aws apigateway put-method \
  --rest-api-id [[API_ID="your API ID"]] \
  --resource-id [[RESOURCE_ID="your /generate resource ID"]] \
  --http-method POST \
  --authorization-type "NONE"

What does this command do?

This creates a POST method on your /generate resource with no authorization. The --authorization-type "NONE" means anyone can call this endpoint. For a learning project, that is fine. In production, you would add authentication.

Now connect the POST method to your Lambda function using a proxy integration.

  • Look up your AWS account ID by running:
aws sts get-caller-identity --query Account --output text
  • Set up the Lambda proxy integration by running this command, replacing the placeholder values:
aws apigateway put-integration \
  --rest-api-id [[API_ID="your API ID"]] \
  --resource-id [[RESOURCE_ID="your /generate resource ID"]] \
  --http-method POST \
  --type AWS_PROXY \
  --integration-http-method POST \
  --uri arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:[[AWS_ACCOUNT_ID="your 12-digit AWS account ID"]]:function:ai-writing-assistant/invocations

What does this command do?

The --type AWS_PROXY tells API Gateway to forward the entire HTTP request to Lambda and pass the Lambda return value back as the HTTP response. This is called Lambda proxy integration.

The --integration-http-method POST is required for all Lambda integrations. Lambda always receives invocations as POST requests, regardless of the client-facing HTTP method.

Next, grant API Gateway permission to invoke your Lambda function.

  • Add the invoke permission by running:
aws lambda add-permission \
  --function-name ai-writing-assistant \
  --statement-id apigateway-invoke \
  --action lambda:InvokeFunction \
  --principal apigateway.amazonaws.com \
  --source-arn "arn:aws:execute-api:us-east-1:[[AWS_ACCOUNT_ID="your 12-digit AWS account ID"]]:[[API_ID="your API ID"]]/*/*/*"

What does this command do?

This adds a resource-based policy to your Lambda function, allowing API Gateway to invoke it. Without this permission, API Gateway would receive a 500 Internal Server Error every time it tried to call your function.

The /*/*/* wildcard at the end of the source ARN grants access for any stage, method, and resource. This keeps the setup simple for a learning project.

Finally, deploy the API to a stage so it becomes callable over the internet.

  • Deploy the API to a prod stage by running:
aws apigateway create-deployment \
  --rest-api-id [[API_ID="your API ID"]] \
  --stage-name prod

Your API is now live. The endpoint URL follows this pattern:

https://[[API_ID="your API ID"]].execute-api.us-east-1.amazonaws.com/prod/generate

What is a stage?

A stage in API Gateway is a named snapshot of your API deployment. Common names are dev, staging, and prod. Each stage gets its own URL. You deployed to prod, which means your endpoint includes /prod/ in the path.

Got an error during any of these commands?

Double-check that you replaced every placeholder (API ID, resource ID, account ID) with the actual values from previous command outputs. A single mismatched ID will cause the command to fail.

If you see a NotFoundException, verify the --rest-api-id and --resource-id values match what was returned by the earlier commands.

Help me debug my API Gateway setup.

Test your live API endpoint

Your API is deployed and live on the internet. Before you run the test, what do you expect to happen when you send a POST request with a topic?

  • Test your API by sending a POST request with curl:
curl -X POST \
  https://[[API_ID="your API ID"]].execute-api.us-east-1.amazonaws.com/prod/generate \
  -H "Content-Type: application/json" \
  -d '{"topic":"serverless computing"}'

You should see a JSON response with a topic field showing serverless computing and an article field containing an AI-generated article about serverless computing.

Getting an Internal Server Error?

If you see a 500 error, the most common cause is a missing Lambda invoke permission. Re-run the aws lambda add-permission command from the previous substep and make sure your API ID and account ID are correct.

If the response takes a long time and then returns an error, your Lambda may still have the old 3-second timeout. Check the Configuration tab in the Lambda Console to verify the timeout is set to 30.

Help me debug my API Gateway curl request.

  • Validate the API in the API Gateway Console by navigating to API Gateway in the AWS Console.
  • Click on AI-Writing-API in the APIs list.
  • Click Resources in the left navigation. Confirm you see the /generate resource with a POST method.
  • Click Stages in the left navigation. Confirm you see the prod stage listed.

You now have a fully working AI writing assistant API. Any application can send a POST request with a topic to your endpoint and get back AI-generated content. In the Secret Mission, you will add AI writing personas that dynamically change the writing style.

Secret mission

Add AI Writing Personas

Your API speaks in one voice right now. What if it could write like a pirate, lecture like a professor, or compose like a poet? Add dynamic persona support to change the AI's writing style with every request.

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. Your Lambda function and API Gateway are serverless, so they cost nothing while idle. The only charges come from Bedrock model invocations when someone calls your API.

Cost warning

Your API Gateway endpoint is publicly accessible. If anyone discovers the URL and sends requests, each call triggers a Bedrock invocation that costs real money ($0.06 per 1M input tokens, $0.24 per 1M output tokens). If you are not actively using the API, choose Pause or Delete below to prevent unexpected charges.

Resources you used:

  • IAM role: ai-writing-lambda-role (with attached policies AWSLambdaBasicExecutionRole and BedrockInvokeAccess).
  • Lambda function: ai-writing-assistant (Python 3.12 runtime, 30-second timeout).
  • API Gateway REST API: AI-Writing-API (deployed to prod stage with /generate POST route).
  • Local files: lambda_function.py, trust-policy.json, bedrock-policy.json, and lambda_function.zip.

Keep everything running

No action needed. Choose this if you are still actively building or experimenting with your API.

  • Your Lambda function and API Gateway are covered by the AWS free tier (1M Lambda requests/month, 1M API Gateway calls/month for 12 months).
  • You only pay for Bedrock invocations when someone actually calls your API. There is no cost while idle.
  • Your API endpoint remains publicly accessible. Keep an eye on your AWS billing dashboard if you leave it running long term.

Pause - I'll come back to this later

Remove the public endpoint so no one can trigger Bedrock invocations while you are away. Your Lambda function and IAM role stay intact for when you return.

  • Delete the API Gateway deployment stage by running:
aws apigateway delete-stage --rest-api-id [[API_ID="your REST API ID"]] --stage-name prod --region us-east-1

This removes the public prod endpoint. Your API definition, Lambda function, and IAM role all remain in your account. When you are ready to resume, redeploy to a new stage.

Delete - I don't want to use this again

Remove all project resources from your AWS account and delete the local files. Run these commands in order.

  • Delete the API Gateway REST API:
aws apigateway delete-rest-api --rest-api-id [[API_ID="your REST API ID"]] --region us-east-1
  • Delete the Lambda function:
aws lambda delete-function --function-name ai-writing-assistant --region us-east-1
  • Detach the managed policy from the IAM role:
aws iam detach-role-policy --role-name ai-writing-lambda-role --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
  • Delete the inline policy from the IAM role:
aws iam delete-role-policy --role-name ai-writing-lambda-role --policy-name BedrockInvokeAccess
  • Delete the IAM role:
aws iam delete-role --role-name ai-writing-lambda-role
  • Delete your local project files:
rm lambda_function.py trust-policy.json bedrock-policy.json lambda_function.zip

All project resources have been removed from your AWS account and your local machine.

Nice Work!

Nice Work!

You just built a serverless AI writing assistant and deployed it as a live REST API. Anyone can send your endpoint a topic and get back a freshly generated article powered by Amazon Bedrock.

You've learned how to:

  • Call Amazon Nova Lite directly from the CLI, then wrap it in a Lambda function that handles prompt construction and response parsing with the Converse API.
  • Diagnose and fix a real-world Lambda timeout caused by Bedrock's response latency, then wire up API Gateway as a public REST endpoint to expose your function to the internet.
  • Deploy and validate every AWS resource (IAM role, Lambda function, API Gateway) entirely through CLI commands, then verify them visually in the AWS Console.
  • Secret Mission: Add AI writing personas that dynamically change the model's writing style using system prompts.

Ready to quiz yourself?