AWS AgentCore + AWS API Gateway


Article 2: Exposing Your Agent via REST API with AWS AgentCore

Series: Backoffice Support Agent with AWS AgentCore

Table of Contents

  1. Introduction
  2. Architecture
  3. Prerequisites
  4. Direct Agent Invocation
  5. Webhook Infrastructure with CDK
  6. Infrastructure Deployment
  7. Post-Deployment Configuration
  8. Testing and Validation
  9. Troubleshooting
  10. Next Steps

Introduction

In Article 1, we deployed our support agent with AWS AgentCore Runtime. The agent is working and can be invoked via the agentcore invoke CLI.

But how do we allow external systems (like ServiceNow) to call our agent? That’s what we’ll solve in this article.

What You Will Build

In this article, we will:

  • βœ… Understand how to invoke the agent via the AWS bedrock-agentcore API
  • βœ… Deploy a complete webhook infrastructure with AWS CDK
  • βœ… Create a secure REST API with API Gateway
  • βœ… Configure a Lambda that bridges the API and the agent
  • βœ… Test everything with curl

Estimated Time

⏱️ 30-45 minutes to complete this tutorial.


Architecture

Overview

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                 β”‚     β”‚                 β”‚     β”‚                 β”‚     β”‚                 β”‚
β”‚  HTTP Client    │────▢│  API Gateway    │───▢│     Lambda      │───▢│  AgentCore      β”‚
β”‚  (curl, app)    β”‚     β”‚  + API Key      β”‚     β”‚  (handler.py)   β”‚     β”‚  Runtime        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β”‚                        β”‚
                              β”‚                        β”‚
                              β–Ό                        β–Ό
                        CloudWatch              Secrets Manager
                          Logs                  (credentials)

Why This Architecture?

  1. API Gateway: Secure entry point with API Key authentication, throttling, and logging
  2. Lambda: Transformation logic between the HTTP request format and the format expected by AgentCore
  3. AgentCore Runtime: Our agent deployed in Article 1
  4. Secrets Manager: Secure storage of credentials (used in Article 3)

Data Flow

  1. A client sends a POST request to /webhook/servicenow with ticket data
  2. API Gateway validates the API Key and forwards to Lambda
  3. Lambda parses the payload, builds the prompt, and calls AgentCore
  4. AgentCore executes the agent that analyzes the ticket
  5. The response flows back up to the client

Prerequisites

From Article 1

  • βœ… Agent deployed with agentcore launch
  • βœ… Agent ARN available in .bedrock_agentcore.yaml

New Prerequisites

  1. Node.js and npm (for AWS CDK)

    node --version  # v18.x or higher recommended
    npm --version
  2. AWS CDK CLI

    npm install -g aws-cdk
    cdk --version
  3. Docker (for Lambda build)

    docker --version

Direct Agent Invocation

Before building the infrastructure, let’s understand how to invoke the agent programmatically.

The bedrock-agentcore Client

AWS provides a specific boto3 client for AgentCore:

import boto3
import json

# Create the bedrock-agentcore client
client = boto3.client('bedrock-agentcore', region_name='eu-central-1')

# Prepare the payload
payload = json.dumps({
    "prompt": "Analyze this ticket: INC0001234 - VPN connection issues"
})

# Invoke the agent
response = client.invoke_agent_runtime(
    agentRuntimeArn='arn:aws:bedrock-agentcore:eu-central-1:ACCOUNT_ID:runtime/AGENT_ID',
    runtimeSessionId='unique-session-id-minimum-33-characters',
    payload=payload,
    qualifier="DEFAULT"
)

# Read the response
response_body = response['response'].read()
response_data = json.loads(response_body)
print("Agent Response:", response_data)

Important Parameters

ParameterDescriptionConstraints
agentRuntimeArnFull ARN of the deployed agentFormat: arn:aws:bedrock-agentcore:REGION:ACCOUNT:runtime/AGENT_ID
runtimeSessionIdUnique session identifierMinimum 33 characters
payloadData to send to the agentStringified JSON with prompt key
qualifierAgent versionDEFAULT for the active version

Retrieving Your Agent ARN

The ARN is found in the .bedrock_agentcore.yaml file generated during deployment:

grep "agent_arn" .bedrock_agentcore.yaml

Example output:

agent_arn: arn:aws:bedrock-agentcore:eu-central-1:653783183133:runtime/agent_level_one_triage-9rGFpG5ZFx

Webhook Infrastructure with CDK

Now, let’s create the infrastructure that will expose our agent via a REST API.

CDK Project Structure

infrastructure/
└── cdk/
    β”œβ”€β”€ bin/
    β”‚   └── app.ts              # CDK entry point
    β”œβ”€β”€ lib/
    β”‚   └── servicenow-webhook-stack.ts  # Stack definition
    β”œβ”€β”€ package.json
    β”œβ”€β”€ tsconfig.json
    └── cdk.json

The CDK Stack

Here are the main components of our stack (lib/servicenow-webhook-stack.ts):

1. IAM Role for Lambda

const lambdaRole = new iam.Role(this, 'WebhookLambdaRole', {
  assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
  managedPolicies: [
    iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'),
  ],
});

// Permission to invoke AgentCore
lambdaRole.addToPolicy(
  new iam.PolicyStatement({
    effect: iam.Effect.ALLOW,
    actions: ['bedrock-agentcore:InvokeAgentRuntime'],
    resources: ['arn:aws:bedrock-agentcore:eu-central-1:ACCOUNT_ID:runtime/*'],
  })
);

Important: The bedrock-agentcore:InvokeAgentRuntime permission is specific to AgentCore. Do not confuse with bedrock:InvokeAgent which is for classic Bedrock Agents.

2. Lambda Function

const webhookHandler = new lambda.Function(this, 'WebhookHandler', {
  runtime: lambda.Runtime.PYTHON_3_12,
  handler: 'handler.lambda_handler',
  code: lambda.Code.fromAsset(path.join(__dirname, '../../../src/webhook')),
  role: lambdaRole,
  timeout: cdk.Duration.seconds(300),  // 5 minutes to give the agent time
  memorySize: 512,
  environment: {
    LOG_LEVEL: 'INFO',
  },
});

3. API Gateway with API Key

const api = new apigateway.RestApi(this, 'ServiceNowWebhookApi', {
  restApiName: 'ServiceNow Webhook API',
  deployOptions: {
    stageName: 'prod',
    loggingLevel: apigateway.MethodLoggingLevel.INFO,
  },
});

// API Key to secure access
const apiKey = new apigateway.ApiKey(this, 'ServiceNowApiKey', {
  apiKeyName: 'servicenow-webhook-api-key',
  enabled: true,
});

// POST route /webhook/servicenow
const webhookResource = api.root.addResource('webhook');
const servicenowResource = webhookResource.addResource('servicenow');
servicenowResource.addMethod('POST', new apigateway.LambdaIntegration(webhookHandler), {
  apiKeyRequired: true,
});

The Lambda Handler

The src/webhook/handler.py file bridges the API and AgentCore:

import boto3
import json
import os
import uuid

# AgentCore client
bedrock_agentcore = boto3.client("bedrock-agentcore", region_name="eu-central-1")
AGENT_RUNTIME_ARN = os.environ.get("AGENT_RUNTIME_ARN")

def generate_session_id(incident_number: str) -> str:
    """Generates a session ID of 33+ characters"""
    base = f"servicenow-{incident_number}-{uuid.uuid4().hex}"
    return base[:50]

def lambda_handler(event, context):
    # Parse the request body
    body = json.loads(event.get("body", "{}"))

    # Extract ticket data
    incident_number = body.get("number", "UNKNOWN")

    # Build the prompt
    prompt = f"""Analyze the following ServiceNow incident:
    Ticket Number: {incident_number}
    Description: {body.get("description", "")}
    Priority: {body.get("priority", "")}
    """

    # Invoke the agent
    response = bedrock_agentcore.invoke_agent_runtime(
        agentRuntimeArn=AGENT_RUNTIME_ARN,
        runtimeSessionId=generate_session_id(incident_number),
        payload=json.dumps({"prompt": prompt}),
        qualifier="DEFAULT"
    )

    # Return the response
    response_body = response["response"].read()
    return {
        "statusCode": 200,
        "body": json.dumps({
            "success": True,
            "analysis": json.loads(response_body)
        })
    }

Infrastructure Deployment

1. Install CDK Dependencies

cd infrastructure/cdk
npm install

2. Bootstrap CDK (first time only)

npm run cdk bootstrap

This command creates the resources needed by CDK in your AWS account.

3. Deploy the Stack

npm run cdk deploy

Deployment takes about 2-3 minutes. At the end, you’ll see the outputs:

Outputs:
ServiceNowWebhookStack.WebhookURL = https://xxxxxx.execute-api.eu-central-1.amazonaws.com/prod/webhook/servicenow
ServiceNowWebhookStack.ApiKeyId = xxxxxxxxxx
ServiceNowWebhookStack.LambdaFunctionName = ServiceNowWebhookStack-WebhookHandler-xxxxx
ServiceNowWebhookStack.GetApiKeyCommand = aws apigateway get-api-key --api-key xxxxxxxxxx --include-value --query 'value' --output text

4. Retrieve the API Key

aws apigateway get-api-key --api-key <API_KEY_ID> --include-value --query 'value' --output text

Note this value, you’ll need it for testing.


Post-Deployment Configuration

Configure the Agent ARN

The Lambda needs to know your agent’s ARN. Configure the environment variable:

aws lambda update-function-configuration \
  --function-name <LAMBDA_FUNCTION_NAME> \
  --environment "Variables={AGENT_RUNTIME_ARN=arn:aws:bedrock-agentcore:eu-central-1:ACCOUNT_ID:runtime/AGENT_ID,LOG_LEVEL=INFO}"

Replace:

  • <LAMBDA_FUNCTION_NAME>: The Lambda function name (see CDK output)
  • ACCOUNT_ID: Your AWS account ID
  • AGENT_ID: Your agent ID (from .bedrock_agentcore.yaml)

Verify the Configuration

aws lambda get-function-configuration \
  --function-name <LAMBDA_FUNCTION_NAME> \
  --query 'Environment.Variables'

Testing and Validation

Test with curl

curl --location 'https://YOUR_API_GATEWAY_URL/prod/webhook/servicenow' \
  --header 'x-api-key: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
    "number": "INC0001234",
    "short_description": "Cannot access email",
    "description": "User cannot send or receive emails since this morning",
    "urgency": "2",
    "impact": "2",
    "priority": "2",
    "category": "Email",
    "assignment_group": "IT Support Level 1"
}'

Expected Response

{
  "success": true,
  "incident_number": "INC0001234",
  "analysis": "**Summary of the issue:**\nUser cannot send or receive emails since this morning...",
  "message": "Incident analyzed successfully"
}

Check CloudWatch Logs

# Find the log group
aws logs describe-log-groups --log-group-name-prefix /aws/lambda/ServiceNowWebhookStack

# View the latest logs
aws logs tail /aws/lambda/ServiceNowWebhookStack-WebhookHandler-xxxxx --follow

Troubleshooting

Error: β€œnot authorized to perform bedrock-agentcore:InvokeAgentRuntime”

Cause: The Lambda’s IAM role doesn’t have the permission.

Solution: Verify that the CDK stack includes:

actions: ['bedrock-agentcore:InvokeAgentRuntime'],

And redeploy with npm run cdk deploy.

Error: β€œAGENT_RUNTIME_ARN environment variable not set”

Cause: The environment variable is not configured.

Solution: Execute the aws lambda update-function-configuration command from the Configuration section.

Error: β€œruntimeSessionId must be at least 33 characters”

Cause: The session ID is too short.

Solution: The handler automatically generates a 50-character ID. If you’re testing manually, make sure to use a sufficiently long ID.

Error: β€œResponse ended prematurely”

Cause: Connection issue with Bedrock (usually temporary).

Solution: Retry after a few seconds. If the problem persists, check:

  • The agent’s region
  • Bedrock quotas
  • Bedrock service status

Error 403 Forbidden

Cause: Missing or invalid API Key.

Solution: Verify that you’re including the x-api-key header with the correct value.


Next Steps

Congratulations! πŸŽ‰ Your agent is now accessible via a secure REST API.

In the next article (Article 3), we will:

πŸ”œ Connect ServiceNow to our API via Business Rules and REST Messages πŸ”œ Modify the agent to update ServiceNow with real API calls πŸ”œ Configure credentials in AWS Secrets Manager πŸ”œ Test the complete flow: ticket creation β†’ automatic analysis β†’ update

Git Branch: step-03-servicenow-integration


Resources

Documentation

Source Code