boto3: Parameter validation failed (ParamValidationError)

TL;DR — botocore.exceptions.ParamValidationError is raised on your machine, before any request is sent — the arguments you passed don't match the operation's expected shape. In DynamoDB code it's almost always a client-vs-resource mixup: the low-level client wants DynamoDB-JSON ({'S': 'abc'}, numbers as strings), the Table resource wants native Python types. Match the style to the interface you're calling.

What it means

botocore.exceptions.ParamValidationError: Parameter validation failed:
Invalid type for parameter Item.price.N, value: 42, type: <class 'int'>,
valid types: <class 'str'>

# what the engine actually returns, reproduced against boto3 1.43.67 on Python 3.11.15:
ParamValidationError: Parameter validation failed:
Invalid type for parameter Item.price.N, value: 42, type: <class 'int'>, valid types: <class 'str'>

botocore validates every call against the service's API model before signing it. A failure here is not a ClientError — DynamoDB never saw the request — so except ClientError won't catch it, and no network round trip happened. The message names the exact parameter path that failed and the type it expected.

Why it happens

  • Native Python values passed to the low-level clientboto3.client('dynamodb') speaks raw DynamoDB JSON: every attribute is a type-tagged map and N values are strings ({'N': '42'}, not 42).
  • DynamoDB-JSON passed to the Table resource — the reverse mixup: boto3.resource('dynamodb').Table(...) expects plain Python values and does the marshalling for you.
  • Condition objects where a string is expected — the query paginator's KeyConditionExpression accepts a string expression; Key('pk').eq(...) objects fail validation there.
  • A typo'd or unsupported parameter name — unknown keys fail validation; an outdated botocore can also reject parameters added to the API after its bundled model.

How to fix it

  1. Pick one interface and use its type style consistently:

    # Table resource — native Python types
    table = boto3.resource('dynamodb').Table('orders')
    table.put_item(Item={'pk': 'ORDER#1', 'price': Decimal('42')})
    
    # Low-level client — DynamoDB-JSON, numbers as strings
    client = boto3.client('dynamodb')
    client.put_item(TableName='orders',
                    Item={'pk': {'S': 'ORDER#1'}, 'price': {'N': '42'}})
  2. Use string expressions with paginatorsKeyConditionExpression='pk = :p' plus ExpressionAttributeValues, or paginate the Table resource manually with LastEvaluatedKey.

  3. Read the parameter path in the messageItem.price.N tells you exactly which attribute and which type tag failed; fix that one field rather than guessing.

  4. Upgrade botocore for "Unknown parameter" failures — if the parameter is real but your validation model predates it, pip install -U boto3 botocore.

  5. Catch it separately from service errors:

    from botocore.exceptions import ClientError, ParamValidationError
    try:
        client.put_item(**kwargs)
    except ParamValidationError as e:   # local: fix the call
        ...
    except ClientError as e:            # remote: DynamoDB rejected it
        ...

Hand-writing DynamoDB-JSON is where these type tags go wrong — the DynamoDB JSON converter translates between native JSON and the type-tagged wire format, and the DynoTable desktop app edits items with the marshalling handled for you.

Reproduce it

Pass a string where boto3 expects the Key mapping. The check is entirely client-side:

import boto3
boto3.client('dynamodb', region_name='us-east-1').get_item(TableName='repro', Key='not-a-dict')

Real output:

ParamValidationError: Parameter validation failed:
Invalid type for parameter Key, value: not-a-dict, type: <class 'str'>, valid types: <class 'dict'>

botocore names the parameter, the value it got, its type, and the type it wanted — four facts in one message. Nothing was sent to AWS, so no capacity was consumed and there is nothing to retry; the fix is always at the call site.

References

Last verified 2026-07-13 against the official AWS documentation linked above.

Reproduced 2026-07-26 against boto3 1.43.56 / botocore 1.43.56 — the output above is verbatim.

Work with DynamoDB without the Console

A fast DynamoDB desktop client that runs the real SQL DynamoDB can’t — JOINs, GROUP BY, aggregates — with visual editing and an AI agent on your own Bedrock keys.

Free 30-day trial, no credit card — then the Free plan with no time limit.