Skip to content

Miro Python Client - Picture model validation fails while calling .get_boards() - model specifies id field as [StrictFloat, StrictInt], but API returns id as string #452

@richbon75

Description

@richbon75

I am using the Miro Python client and getting a Pydantic model validation error when calling .get_boards()

I believe the issue is that the GET https://api.miro.com/v2/boards API endpoint returns the Picture.id field as a string representation of an integer, but in the Picture model, the id field is specified as either a StrictFloat or StrictInt, neither of which allow strings.

The Picture model id field is specified in packages/miro-api-python/miro_api/models/picture.py#L3 :

   id: Optional[Union[StrictFloat, StrictInt]] = Field(default=None, description="Id of the picture")

I can fix the error by modifying it to id: Optional[Union[StrictFloat, StrictInt, int]] to allow not-strict integers, but the project commit messages suggest it employs some kind of automated model generation so I assume this is not a suitable submission for a fix.

When calling the endpoint:

curl --request GET \
     --url https://api.miro.com/v2/boards \
     --header 'accept: application/json' \
     --header 'authorization: Bearer XXXXXXXXXXXXXXXXXXXX'

In the results I see picture id returned as a string:

      "picture": {
        "id": "3458764517504699683",
        "type": "picture",
        "imageURL": "https://mirostatic.com/board-image-assets/20250610/540x540/board_icon_13.png?etag=20250610"
      },

Simplest code to reproduce error (assumes one or more boards exist):

import os
import dotenv
import miro_api

dotenv.load_dotenv()

miro = miro_api.MiroApi(os.getenv("MIRO_ACCESS_TOKEN"))

boards = miro.get_boards()

for board in boards.data:
    print(f"{board.id=} {board.name=}")

Error message I receive:

Traceback (most recent call last):
  File "/usr/lib/python3.12/runpy.py", line 198, in _run_module_as_main
    return _run_code(code, main_globals, None,
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/runpy.py", line 88, in _run_code
    exec(code, run_globals)
  File "/home/richbon/.vscode-server/extensions/ms-python.debugpy-2025.18.0-linux-x64/bundled/libs/debugpy/adapter/../../debugpy/launcher/../../debugpy/__main__.py", line 71, in <module>
    cli.main()
  File "/home/richbon/.vscode-server/extensions/ms-python.debugpy-2025.18.0-linux-x64/bundled/libs/debugpy/adapter/../../debugpy/launcher/../../debugpy/../debugpy/server/cli.py", line 508, in main
    run()
  File "/home/richbon/.vscode-server/extensions/ms-python.debugpy-2025.18.0-linux-x64/bundled/libs/debugpy/adapter/../../debugpy/launcher/../../debugpy/../debugpy/server/cli.py", line 391, in run_module
    run_module_as_main(options.target, alter_argv=True)
  File "/home/richbon/.vscode-server/extensions/ms-python.debugpy-2025.18.0-linux-x64/bundled/libs/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_runpy.py", line 228, in _run_module_as_main
    return _run_code(code, main_globals, None, "__main__", mod_spec)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/richbon/.vscode-server/extensions/ms-python.debugpy-2025.18.0-linux-x64/bundled/libs/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_runpy.py", line 118, in _run_code
    exec(code, run_globals)
  File "/home/richbon/coding/miro_test/src/main.py", line 9, in <module>
    boards = miro.get_boards()
             ^^^^^^^^^^^^^^^^^
  File "/home/richbon/coding/miro_test/.venv/lib/python3.12/site-packages/pydantic/_internal/_validate_call.py", line 39, in wrapper_function
    return wrapper(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/richbon/coding/miro_test/.venv/lib/python3.12/site-packages/pydantic/_internal/_validate_call.py", line 136, in __call__
    res = self.__pydantic_validator__.validate_python(pydantic_core.ArgsKwargs(args, kwargs))
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/richbon/coding/miro_test/.venv/lib/python3.12/site-packages/miro_api/api/__init__.py", line 10589, in get_boards
    return self.api_client.response_deserialize(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/richbon/coding/miro_test/.venv/lib/python3.12/site-packages/miro_api/api_client.py", line 282, in response_deserialize
    return_data = self.deserialize(response_text, response_type)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/richbon/coding/miro_test/.venv/lib/python3.12/site-packages/miro_api/api_client.py", line 351, in deserialize
    return self.__deserialize(data, response_type)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/richbon/coding/miro_test/.venv/lib/python3.12/site-packages/miro_api/api_client.py", line 394, in __deserialize
    return self.__deserialize_model(data, klass)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/richbon/coding/miro_test/.venv/lib/python3.12/site-packages/miro_api/api_client.py", line 659, in __deserialize_model
    return klass.from_dict(data)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/home/richbon/coding/miro_test/.venv/lib/python3.12/site-packages/miro_api/models/boards_paged_response.py", line 125, in from_dict
    "data": [Board.from_dict(_item) for _item in obj["data"]] if obj.get("data") is not None else None,
             ^^^^^^^^^^^^^^^^^^^^^^
  File "/home/richbon/coding/miro_test/.venv/lib/python3.12/site-packages/miro_api/models/board.py", line 193, in from_dict
    "picture": Picture.from_dict(obj["picture"]) if obj.get("picture") is not None else None,
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/richbon/coding/miro_test/.venv/lib/python3.12/site-packages/miro_api/models/picture.py", line 98, in from_dict
    _obj = cls.model_validate(
           ^^^^^^^^^^^^^^^^^^^
  File "/home/richbon/coding/miro_test/.venv/lib/python3.12/site-packages/pydantic/main.py", line 716, in model_validate
    return cls.__pydantic_validator__.validate_python(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
pydantic_core._pydantic_core.ValidationError: 2 validation errors for Picture
id.float
  Input should be a valid number [type=float_type, input_value='3458764517504699683', input_type=str]
    For further information visit https://errors.pydantic.dev/2.12/v/float_type
id.int
  Input should be a valid integer [type=int_type, input_value='3458764517504699683', input_type=str]
    For further information visit https://errors.pydantic.dev/2.12/v/int_type

I am running Python 3.12.3 and this is the pip freeze output of my virtual environment, set up today:

annotated-types==0.7.0
black==25.12.0
click==8.3.1
miro_api==2.2.4
mypy_extensions==1.1.0
packaging==25.0
pathspec==0.12.1
platformdirs==4.5.1
pydantic==2.12.5
pydantic_core==2.41.5
python-dateutil==2.9.0.post0
python-dotenv==1.2.1
pytokens==0.3.0
six==1.17.0
typing-inspection==0.4.2
typing_extensions==4.15.0
urllib3==1.26.20

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions