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'>

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.

References

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

Work with DynamoDB without the Console

DynoTable is a fast desktop client for DynamoDB — browse tables, run SQL-style queries, and edit items locally.