Call Amazon Bedrock from Python behind an API

Published

From the course: AWS in Action: AI edition, session 2: The helpdesk's first answer: a hosted model behind an API

You have a useful prompt working in a local script. The next question is practical: how do you expose it through an HTTP endpoint without giving the function broad permissions, leaking failures, or leaving resources behind?

A small serverless API is enough. An API Gateway HTTP API receives POST /ask, a Python AWS Lambda function validates the question, and the Amazon Bedrock Converse API sends it to Amazon Nova Lite. One CloudFormation stack owns the endpoint, permissions, logs, and function, making the example easier to deploy, inspect, and delete as a unit.

The request contract keeps invalid input away from the model

The endpoint accepts a JSON request containing a question. The question must be text, must not be empty, and must contain no more than 1,000 characters. A question of exactly 1,000 characters is accepted.

A missing question, an empty question, a non-text value, or a question longer than the limit receives HTTP 400. The function performs these checks before calling Bedrock, so rejected requests do not consume model inference.

For example, this is a valid request:

{
  "question": "How do I reconnect to the office printer?"
}

A successful response uses HTTP 200 and has one public field:

{
  "answer": "..."
}

Keeping the contract this small makes it clear which values are supplied by the caller and which controls belong to the application. The caller supplies the question; the stack controls the model, system prompt, token limit, and temperature.

Two parameters decide which model id the function calls

The CloudFormation template has a ModelId parameter that allows exactly two values: amazon.nova-lite-v1:0 (the default) and amazon.nova-micro-v1:0. Nova Micro has no Asia Pacific route, so Nova Lite is the safer choice in most Regions. To switch, update the stack and pick the other value.

A second parameter, ModelRoute, decides how the call reaches the model. Most Regions serve Nova Lite only through an inference profile: a model id with a geography in front of it, such as apac.amazon.nova-lite-v1:0. Think of it as a regional front desk that forwards your call to a Region in that geography which has capacity. A few Regions are called directly with the plain model id.

With the default ModelRoute of auto, the template looks your Region up in a small table and picks the route: ap-south-1 uses apac, eu-west-1 uses eu, us-east-1 uses us, and a Region such as eu-west-2 is called directly. You can also set ModelRoute to direct, us, eu, apac or ca yourself.

The template puts the resulting id into the function’s MODEL_ID environment variable, so MODEL_ID is either the plain foundation model id or the inference profile id. The stack’s ModelInUse output shows which one it chose. The Python code simply reads MODEL_ID, so the model id is not copied into the source.

Check the model page in the Amazon Bedrock documentation for where the model is available. Sign in as an IAM user or through IAM Identity Center, not as the root user.

The Converse API keeps the model call compact

The Converse API provides a common message structure for supported Bedrock models. Here, the system prompt gives the model a narrow role: it is an IT helpdesk assistant, it should answer in no more than five short steps, and, when unsure, it should say so and suggest contacting IT.

A suitable value for SYSTEM_PROMPT is:

You are the IT helpdesk assistant for a company. Answer in at most five short steps. If you are not sure, say so and suggest contacting the IT team.

After validation, the function makes this core call:

bedrock = boto3.client("bedrock-runtime")

response = bedrock.converse(
    modelId=MODEL_ID,
    system=[{"text": SYSTEM_PROMPT}],
    messages=[{"role": "user", "content": [{"text": question}]}],
    inferenceConfig={"maxTokens": 400, "temperature": 0.2},
)
answer = response["output"]["message"]["content"][0]["text"]

The function serializes answer as JSON under the answer key. The maxTokens setting bounds the generated response, while the low temperature favours more consistent helpdesk-style answers.

The system prompt guides the model but does not prove that every answer is correct. That is why its uncertainty instruction matters: the model should acknowledge doubt and direct the user to IT instead of presenting an uncertain answer as established fact.

Failures stay useful to operators and safe for callers

A hosted-model call can fail because of permissions, model availability, quotas, throttling, configuration, or a service error. The function catches failures from the model call, logs the exception, and returns HTTP 502 with a generic safe message.

This creates two different views of the same failure:

Audience What it receives
API caller A stable 502 response without internal exception details
Operator The logged exception in the function’s CloudWatch log group

The caller does not need an AWS exception, resource ARN, or stack detail. The operator does need enough information to diagnose the failure. Logging the exception while keeping it out of the response serves both needs.

Avoid logging the complete request by default. Helpdesk questions can contain names, device details, or other operational information. Log the exception and useful request metadata without treating the question itself as harmless diagnostic data.

The execution role has one job and one model

The Lambda execution role should not receive broad Bedrock access. It is allowed one Bedrock action, bedrock:InvokeModel, which this non-streaming call needs, and only on what the stack chose.

When the call goes through an inference profile, the role allows that profile in the stack’s Region and the foundation model in any Region, because a profile may send the call to any Region in its geography. When the call is direct, it allows only the foundation model in the stack’s Region.

Its logging permissions are also restricted to the function’s own CloudWatch log group. The role can create log streams and write log events there, but it does not need access to unrelated log groups.

Think of the role as a key cut for two doors: the function’s own logs and the chosen model. A wildcard key would make the example easier to type, but it would weaken the boundary.

Changing ModelId therefore has two coordinated effects: the function receives the new id through its environment, and the role’s permission is rebuilt for it. Editing MODEL_ID on the function alone fails, because the role only allows the model the stack chose.

One CloudFormation stack owns the complete path

CloudFormation describes the deployment as a collection of related resources. Creating the stack builds the request path and its permissions; deleting the stack removes the resources it owns.

The template is intentionally explicit:

Resource Purpose
CloudWatch log group Stores logs for this Lambda function and gives the stack ownership of log retention and deletion
IAM execution role Allows the function to write only its own logs and invoke only the chosen model (or its inference profile)
Lambda function Runs the Python handler, reads MODEL_ID, validates the request, and calls Bedrock
API Gateway HTTP API Provides the public HTTP endpoint
Lambda integration Connects the HTTP API to the function
POST /ask route Accepts questions at the intended method and path
API stage Deploys the route and applies its throttle settings
Lambda invoke permission Allows this API to invoke this function

The function, role, and API should refer to one another through CloudFormation attributes rather than copied identifiers. This lets CloudFormation calculate creation order and update references consistently.

The stack should also output the deployed endpoint so you do not have to reconstruct it. That output is deployment-specific rather than part of the Lambda source.

Throttling reduces exposure but does not secure an open endpoint

The API stage sets a burst limit of 5 and a steady rate of 1 request per second for the route. The burst setting allows a small, short-lived cluster of requests; the steady rate governs the continuing request rate.

API Gateway applies throttling on a best-effort basis. These settings can limit the cost exposure of an open endpoint, but they are not a hard guarantee and are not a substitute for sign-in.

That distinction matters. Throttling answers “how quickly may requests generally arrive?” Authentication answers “who may make them?” A real internal helpdesk API should normally add an appropriate identity control before it is exposed to users.

Offline tests prove behaviour without calling Bedrock

The handler can be tested offline by replacing the Bedrock client with a fake model client. The fake records the arguments supplied to converse and returns a small response shaped like the Bedrock result.

The test suite should establish these behaviours:

Test Expected result
Valid question HTTP 200 with the fake answer in the answer field
Model configuration The fake receives the configured model identifier
User message The fake receives the original question text
System instruction The fake receives a system prompt
Empty question HTTP 400, with no model call
Missing question HTTP 400, with no model call
Question over 1,000 characters HTTP 400, with no model call
Model exception HTTP 502, with no internal exception detail in the response

It is also worth testing the boundary directly: a 1,000-character question should reach the fake model, while a 1,001-character question should not.

These tests separate handler correctness from AWS availability. They can catch accidental changes to validation, model selection, where the system prompt goes, and error handling without credentials, network access, or inference cost.

A fake cannot prove that the selected model is available in the deployment Region or that the IAM resource ARN is correct. Those are integration concerns checked after deployment.

A live request verifies the complete route

Once the stack is created, take its endpoint output and send a request to /ask:

curl -i \
  -X POST \
  -H 'content-type: application/json' \
  -d '{"question":"How do I reconnect to the office printer?"}' \
  'https://YOUR_API_ENDPOINT/ask'

A successful call should return HTTP 200 and a JSON object containing answer. Then try an invalid request and confirm that it returns HTTP 400 without reaching the model.

If the valid request returns 502, open the function’s CloudWatch log group. The logged exception should help distinguish an IAM problem from an unavailable model, an unsupported Region, or another Bedrock failure. The public response should remain generic even while the log contains the operational detail.

After testing, delete the CloudFormation stack. Because the log group, role, function, API resources, integration, route, stage, and invoke permission belong to that stack, deletion removes the example as a unit so none of those resources remains deployed and able to incur further cost.

Key takeaways

  • Choose amazon.nova-lite-v1:0 or amazon.nova-micro-v1:0 with ModelId; ModelRoute (default auto) turns it into an inference profile id such as apac.amazon.nova-lite-v1:0 where the Region needs one, and the function reads the result from MODEL_ID.
  • Reject missing, empty, non-text, and over-1,000-character questions before calling the model; accept exactly 1,000 characters.
  • Return only {"answer": ...} on success, and keep model failure details in logs rather than the 502 response.
  • Restrict the execution role to its own logs and the one model the stack chose.
  • Treat API Gateway throttling as best-effort cost control, not as a hard limit or user authentication.
  • Test handler behaviour with a fake client before verifying the deployed route with curl.
  • Delete the stack after the exercise so its resources do not remain active.

This design settles the core path from an HTTP request to a Bedrock response. It does not settle production authentication, abuse prevention, audit requirements, sensitive-data handling, or model-answer evaluation; those controls depend on who will use the helpdesk endpoint and what information they may send.


This post is made from chapter 2 of AWS in Action: AI edition, where you build and test it step by step. To go further with mentoring, see the Cloud and AI Solutions Architect program.