amazon-web-servicesamazon-bedrockanthropic

Automating access to models in AWS Bedrock


I'm trying to write a script that keeps the available Bedrock models in sync across multiple accounts (primarily to be sure that we have the same models available in DR as we do in production).

I've figured out how to check for and submit the use case (see this question https://stackoverflow.com/a/79717632/3990127) but there are two other flags that need to be flipped.

A call to aws bedrock get-foundation-model-availability --model-id anthropic.claude-3-haiku-20240307-v1:0 --region eu-west-1 --profile prod returns a data structure such as:

{
    "modelId": "anthropic.claude-3-haiku-20240307-v1",
    "agreementAvailability": {
        "status": "NOT_AVAILABLE"
    },
    "authorizationStatus": "AUTHORIZED",
    "entitlementAvailability": "NOT_AVAILABLE",
    "regionAvailability": "AVAILABLE"
}

In a model that I've signed up for via the console, both agreementAvailability and entitlementAvailability are AVAILABLE.

I've figured out that some APIs which were recently made public include ListFoundationModelAgreementOffers and CreateFoundationModelAgreement. These can be used to flip agreementAvailability by getting an offer token from the list endpoint and using it to create an agreement by submitting the token to the create endpoint (this programatically agrees a set of terms and conditions which are available from the list endpoint as a PDF).

This leaves me with the entitlementAvailability and I've not been able to figure out how to flip that. AWS support are suggesting starting a change set in the market place, but I can't figure out how to get a product ID from the model ID.


Solution

  • After a lot of experimentation and reverse engineering the console I figured out how to do this.

    There is an undocumented API, `FoundationModelEntitlement`, that is key to flipping the other field.

    It's a very simple API that lives at https://bedrock.{region}.amazonaws.com/foundation-model-entitlement. It takes a POST with a body of {'modelId': model_id}.

    I think that this API should have been included in those that they made public in June '25. Hopefully it will be added soon - I've asked for a product feature request to be created for exactly that.

    FWIW, my test python call to this looks like this (just using boto3 dependencies):

        try:
            session = boto3.Session(profile_name=environment)
            credentials = session.get_credentials()
            
            # Prepare the request data
            url = f'https://bedrock.{region}.amazonaws.com/foundation-model-entitlement'
            payload = json.dumps({'modelId': model_id})
            
            print(f"Making request to: {url}")
            print(f"Payload: {payload}")
            
            # Create a simple AWSRequest with minimal headers
            request = AWSRequest(
                method='POST',
                url=url,
                data=payload,
                headers={'Content-Type': 'application/x-amz-json-1.1'}
            )
            
            # Sign the request
            SigV4Auth(credentials, 'bedrock', region).add_auth(request)
            
            print(f"Request headers after signing: {dict(request.headers)}")
            
            # Convert to urllib request
            urllib_request = urllib.request.Request(
                url,
                data=request.body,
                headers=dict(request.headers),
                method='POST'
            )
            
            # Make the request
            try:
                with urllib.request.urlopen(urllib_request, timeout=30) as response:
                    status_code = response.status
                    response_headers = dict(response.headers)
                    content = response.read().decode('utf-8')
                    
                    print(f"Response status: {status_code}")
                    print(f"Response headers: {response_headers}")
                    print(f"Response content: {content}")
                    
                    try:
                        response_json = json.loads(content) if content else {}
                        return {"status": "success", "response": response_json, "http_status": status_code}
                    except json.JSONDecodeError:
                        return {"status": "success", "response": content, "http_status": status_code}
                        
            except urllib.error.HTTPError as http_error:
                status_code = http_error.status
                error_content = http_error.read().decode('utf-8')
                print(f"HTTP Error {status_code}: {error_content}")
                return {"status": "http_error", "http_status": status_code, "error": error_content}
                
        except Exception as e:
            print(f"✗ Error calling entitlement API: {str(e)}")
            return {"status": "error", "error": str(e)}