Create workflow and experiment records

OSC describes workflows and experiments using OGC API Records. A workflow names its project using osc:project; an experiment names its workflow using osc:workflow. Write these into record properties directly: OscExtension.apply_project() and apply_product() describe STAC project/product shapes, not these records.

import pystac
from pystac.extensions.ogc_record import OGCRecord

workflow = OGCRecord(
    id="ocean-workflow",
    properties={
        "type": "workflow",
        "title": "Ocean temperature workflow",
        "description": "Processing steps for sea surface temperature",
        "osc:project": "ocean-science",
    },
)
workflow.add_link(pystac.Link(
    rel="related", target="https://example.org/projects/ocean-science.json",
))
workflow.add_link(pystac.Link(
    rel="child", target="https://example.org/experiments/experiment-42.json",
))

experiment = OGCRecord(
    id="experiment-42",
    properties={
        "type": "experiment",
        "title": "Ocean workflow execution 42",
        "description": "An execution producing the ocean-temperature product",
        "osc:workflow": workflow.id,
    },
)
experiment.add_link(pystac.Link(
    rel="related", target="https://example.org/workflows/ocean-workflow.json",
))
experiment.add_link(pystac.Link(
    rel="child", target="https://example.org/products/ocean-temperature.json",
))
experiment.add_link(pystac.Link(
    rel="environment", target="https://example.org/experiments/environment.yml",
))
experiment.add_link(pystac.Link(
    rel="input", target="https://example.org/experiments/parameters.json",
))

experiment.keywords = ["ocean", "temperature"]
experiment.language = {"code": "en"}
document = experiment.to_record_dict()
assert document["type"] == "Feature"
assert document["properties"]["type"] == "experiment"
assert "stac_version" not in document
restored = OGCRecord.from_dict(document)
assert restored.properties["osc:workflow"] == "ocean-workflow"

The example's workflow and experiment resource type strings describe the application; they are not OscType enum members. It demonstrates the OSC relationship conventions, without asserting full OGC conformance. No OSC STAC schema declaration is added to these records: that schema only accepts project/product shapes.

Read, write, and copy

Use OGCRecord.from_dict(document) on decoded JSON. Use json.dumps(record.to_dict()) to serialize it. Pass href= to from_dict() to set the self link, replacing any incoming self link. Metadata is copied even when preserve_dict=False; migrate is accepted for PySTAC compatibility but has no effect.

record.clone() copies metadata and clones links and assets. record.record_metadata is a live view of record.properties; its to_dict() returns a deep copy. Assigning None to a common metadata accessor removes that property.

Export a STAC Item

from datetime import datetime, timezone

record = OGCRecord(
    id="observation",
    datetime=datetime(2026, 1, 1, tzinfo=timezone.utc),
    properties={"type": "dataset", "title": "Observation"},
)
item = record.to_stac_item()
assert "stac_version" in item.to_dict()

STAC export requires datetime or both start_datetime and end_datetime. An OGC time member alone does not satisfy this requirement. Export does not validate the resulting Item. See validation boundaries.