Skip to content

ImageAsset.format / alt_text / provenance — Pydantic type allows null but bundled schema does not #622

@bokelley

Description

@bokelley

Mismatch

The Python library type for ImageAsset (and sibling asset variants) declares optional fields as nullable:

```python
class ImageAsset(BaseModel):
asset_type: Literal["image"] = "image"
url: str
width: int
height: int
format: str | None = None
alt_text: str | None = None
provenance: Provenance | None = None
```

The bundled JSON schema (`creative/list-creatives-response.json`, AdCP 3.0.6 +) declares the same fields as non-nullable scalar / object types:

```json
"format": { "type": "string" },
"alt_text": { "type": "string" },
"provenance": { "type": "object", ... }
```

Result: an adopter who happily round-trips the typed model and emits the wire envelope via `model_dump()` includes `format: null` / `alt_text: null` / `provenance: null`. The storyboard SDK's bundled-schema validator rejects this — the asset value matches multiple branches of the AssetVariant `oneOf` (or none), depending on validator semantics.

Repro

Storyboard `media_buy_seller/creative_fate_after_cancellation/list_creatives_before_cancel` against an adopter that returns Pydantic-built `ListCreativesResponse` with an image asset that has no format / alt_text / provenance. Validator fails with `Invalid input` at `/creatives/0/assets/`.

Workaround on the adopter side

Strip null-valued fields from each asset value before `model_dump` runs:

```python
assets_dict = {
key: ({k: v for k, v in value.items() if v is not None or k not in _NULL_STRIP_FIELDS}
if isinstance(value, dict) else value)
for key, value in assets_dict.items()
}
```

bokelley/salesagent does this in `src/core/tools/creatives/listing.py`. Workable but every adopter has to do it.

Suggested fix

Two options:

  1. Match bundled schema: tighten the Pydantic types to non-nullable. Breaking change for adopters constructing models with explicit `None`s.
  2. Match Pydantic types: relax the bundled schema to allow null. Spec-level change.

Option 1 is the more conservative read — bundled schemas are the authoritative wire contract for compliance grading. Option 2 widens the wire surface and risks downstream consumers (storyboard validator, third-party buyers) tripping on `null` they don't expect.

If option 1: also flip the model_dump default to `exclude_none=True` on the response wrapper so legacy adopters keep emitting valid wire payloads after the type tightening.

Related

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions