Uploading is the one part of the media library a browser tab and the API cannot do for a folder: it reads files off your own machine.
Directories are expanded one level deep and only renderable assets are taken (png, jpg, jpeg, gif, webp, avif, svg, ico). One level rather than a recursive walk, because pointing at a project directory would otherwise upload node_modules. A file you name explicitly is always taken, whatever its extension.

The size ceiling is about 1 MB

A photograph straight off a phone or a camera will not fit. The limit comes from the idempotency ledger, which buffers the whole request body to hash it, not from storage. It applies before the upload route runs.
Sizes are checked before anything is sent. One oversized file stops the batch:
Finding this out on file 51 of 200 would leave a half-filled library nobody can reason about, so the whole batch is refused until you decide.
Skipped files are then listed by name at the end, not counted. “3 skipped” tells nobody which images are missing from their store.

See the plan first

A dry run sends nothing and reports oversized files as part of the plan rather than refusing, because seeing what would happen is the reason to ask.

Running out of storage

Uploads spend the store’s storage allowance. If one is refused with storage_quota_exceeded the batch stops immediately rather than sending the rest:
That refusal never succeeds on retry. The merchant frees space or upgrades, and continuing would spend rate budget to arrive at the answer already in hand.

Scripting

Needs the write_files scope.