Real estate listing video: a typed output schema with SumeMediaFile

Bind an output_schema to a Format run so a listing video returns as your JSON: address, headline, caption and a SumeMediaFile to store beside the MLS ID.

6 min readSume
All posts

To get a listing video back as data you can write straight to a listings table, bind an output_schema to the Format run: the root is an object, each field is required, and the video is a SumeMediaFile reference that Sume checks was really produced by that run. The run then returns your own JSON, for example an address, a headline, a caption and the video, rather than the built-in projection.

The rules below come from Format structured output and Calling a Format. The schema is an example for a real estate team, with a placeholder acme/listing-video Format.

What does the schema have to look like?

Sume accepts a subset of JSON Schema. The root must be an object, every object needs additionalProperties: false, every property must be listed in required, and every node needs a type. Optional fields are expressed as a type union with null, such as ["string", "null"], not as a missing property. strict: false does not relax any of this.

Media fields use { "$ref": "SumeMediaFile#" }. Every field of a SumeMediaFile is required and every field except type and url is nullable, so the video's duration_ms, width and height come back for your records when the run knows them. A media field such as cover must be something the Format run itself produced; a URL the run did not generate is refused by the same check.

Rules the listing schema follows (read 2026-10-03)
RuleApplied in the example
Root is an objectYes: one object with five properties
additionalProperties false on every objectYes
Every property in requiredYes; the optional caption is a string-or-null union
Media by SumeMediaFile referencevideo and cover use the $ref
Name 1-64 characters, A-Z a-z 0-9 . _ / -acme/listing-video/v1

How to bind it to a run

Send output_schema with a name, strict and the schema, and name the key that holds the deliverable with primary_output_key. The receipt echoes which schema applied under output_schema.source, with request_override for a per-request schema. If you send response_format as well as output_schema, the request is a 400.

Add a spend cap and an idempotency key derived from the MLS id, so a retry does not start a second paid run. The example uses the placeholder Format acme/listing-video; swap in yours. The listing facts go in input, which is data, while the style goes in instruction.

import hashlib
import os
import requests

MEDIA = {"$ref": "SumeMediaFile#"}
SCHEMA = {
    "type": "object",
    "additionalProperties": False,
    "required": ["address", "headline", "caption", "video", "cover"],
    "properties": {
        "address": {"type": "string"},
        "headline": {"type": "string"},
        "caption": {"type": ["string", "null"]},
        "video": MEDIA,
        "cover": MEDIA,
    },
}


def start_listing_video(mls_id, address, photo_urls):
    key = hashlib.sha256(f"{mls_id}:listing-video:v1".encode()).hexdigest()[:40]
    body = {
        "instruction": "Vertical 9:16 tour from the photos. Use the address as written.",
        "input": {"mls_id": mls_id, "address": address, "photo_urls": photo_urls},
        "output_schema": {
            "name": "acme/listing-video/v1",
            "strict": True,
            "schema": SCHEMA,
        },
        "primary_output_key": "video",
        "generation_spend_cap_usd": 25,
    }
    r = requests.post(
        "https://api.sume.com/v1/formats/acme/listing-video/runs",
        headers={
            "Authorization": "Bearer " + os.environ["SUME_API_KEY"],
            "Idempotency-Key": key,
        },
        json=body,
        timeout=30,
    )
    r.raise_for_status()
    return r.json()["data"]["id"]

What can still go wrong

The URL gate rejects a media URL that the run did not produce, so a field cannot be filled with one of the photos you uploaded. A schema the run cannot satisfy ends in an output error on the receipt instead of a half-filled object. Check primary_output_url before you publish, and keep the MLS id in your own table beside the run id.

Sume-hosted media is served from media.sume.com and does not expire, but a durable URL is also a public one. If a listing is pre-market and confidential, keep that in mind before you store or share the link.

Sources

Related posts

More in Formats

All Formats posts

Written by Sume