Changelog

Notable changes to the Zentag Partner API.

All notable changes to the Zentag Partner API are documented here. The API is
versioned in the URL path (/api/v1/partner). Backwards-incompatible changes
ship under a new major version; additive changes may ship within v1.

v1.2.1 - 2026-09-01

Added

  • GET /matches — search fixtures for your organization. Returns
    { object: "match", match_id, name, team1_id, team2_id }. Pass competition_id
    (from GET /competitions) to search that competition's season schedule; optional
    search / q filters by name or id; optional match_id fetches one fixture
    exactly. Use team1_id / team2_id and match_id on POST /streams.
  • match_id on streams — accepted on POST /streams and PATCH /streams/{id},
    and returned on stream reads. Same fixture id Studio uses when creating a video
    feed from a schedule match.

v1.2.0 - 2026-08-24

Stream ingest and templates, expanded media and highlight filtering, webhook
correlation, and a closed allowlist for identifiers.

⚠️ Breaking

  • External storage push records are now snake_case. GET /clips/{id}/external-storage-status
    and the push object inside POST /clips/{id}/push-external-storage and the
    bulk variant previously returned camelCase keys (bucketId, videoObjectKey,
    pushedAt, …), inconsistent with the rest of the API. They now return
    bucket_id, video_object_key, pushed_at and so on, plus replaced_at.
  • Player tag metadata is a closed allowlist. It now contains only
    player_name, short_name, position, jersey_number, nationality and
    roster_memberships (themselves reduced to jersey_number and position).
    Upstream sports-data-provider identifiers that previously leaked through this
    field are removed and will not return. The same applies to the JSON sidecar
    written when a clip is pushed to external storage.
  • /media/stats and /media/filters/counts return a fixed, documented shape.
    Both previously forwarded the internal Studio response wholesale. Buckets are
    now explicitly named and snake_case, and facet buckets appear only when
    computed. In match_index, the provider match id is replaced by an opaque,
    non-reversible match_ref.

Added

  • POST /streams can run the processing pipeline. Supplying the new optional
    video_template_id makes Zentag provision ingest and analyse the feed, along
    with video_type, match_date, scheduled_start_at, scheduled_end_at,
    scheduled_duration_hours and timezone. Omitting it keeps the existing
    metadata-only behaviour, so no existing integration changes.
  • GET /stream-templates lists the templates your organization can pass as
    video_template_id, with input type, resolution, frame rate, audio tracks and
    whether AI analysis runs.
  • GET /streams/{id}/ingest returns push ingest coordinates for RTMP_PUSH and
    SRT_PUSH streams. Scoped streams:write rather than streams:read, because
    the response includes a stream key.
  • Stream gains status, video_type, aspect_ratio, match_date,
    clips_count, highlights_count, tags, scheduled_start_at,
    scheduled_end_at, timezone and updated_at. Prefer status over is_live
    when tracking progress: is_live is false for pending, completed, failed,
    cancelled, delayed and scheduled alike.
  • GET /streams gains status, is_live, video_type, competition_id,
    search, created_after, created_before, match_date_after,
    match_date_before and sort_by.
  • Webhook event stream.created, emitted for streams created through the API and
    in Zentag Studio, so an integration can pick up streams it did not create.
  • New guide: Streams.
  • GET /clips/{id}/jobs and GET /jobs/{id} to reconcile async job state after
    a missed webhook delivery, without re-running the render.
  • Webhook payloads carry job_id and idempotency_key, correlating a delivery
    with the request that started the work. Both are null for events originating
    in the Studio UI.
  • Clip.variants[] (aspect ratio, video url, thumbnail, status) so a
    clip.ready event shows what was produced.
  • Highlight gains category, clip_ids, rating, tags, preview_url,
    thumbnail_url, updated_at and completed_at.
  • GET /highlights gains category, rating, is_ai_created, duration
    buckets, start_date, end_date and sort_by.
  • GET /media gains the full filter vocabulary of the Zentag Studio library:
    hierarchy filters and the facet controls counts_bundle,
    include_match_index, facet_page_size, facet_page and facet_load_more.
  • MediaItem gains download_status, platform, players, actions,
    venue, season, match_day, duration_label, score_label, progress
    and variants.
  • New guide: Media library filters. Webhooks expands into a per-event
    reference with explicit ordering and deduplication guarantees.

Fixed

  • GET /media pagination was stuck: next_cursor did not advance, so every
    page returned the first page.
  • GET /media?stream_id= filtered against the stream title facet instead of
    scoping to the stream. It now scopes correctly.
  • Clip lifecycle events were missing in several paths: AI scene creation,
    saveClipAsNew, editor render completion, and the AI trim webhook's
    completion and failure branches. All now emit.
  • stream.started was emitted by POST /streams, which only registers
    metadata. It now fires when the stream actually goes live.
  • highlight.ready was emitted when an AI job was accepted, not when the reel
    finished. The premature event is gone; the real one fires on completion.
  • clip.ready for crop jobs fired on acceptance rather than on completion.
  • stream.ended could be emitted twice for an already-ended stream.
  • Webhook retry backoff only covered 5 of the 6 attempts; the last retry reused
    the previous delay.
  • Webhook fan-out is now scoped by partner app, so an app only receives events
    for endpoints it registered.
  • Deleted streams were still reachable. GET /streams, GET /streams/{id},
    the nested clip and highlight lists, PATCH /streams/{id} and
    POST /streams/{id}/end did not exclude deleted streams, and a deleted stream
    could still be named as the target of a new clip or highlight. All now treat a
    deleted stream as absent.
  • team1_id, team2_id and competition_id were accepted on write but not
    returned on read.
    They now round-trip.

Documentation

  • Spec bumped to 1.2.0. All 13 untyped 2xx responses now have concrete
    schemas, the two generic placeholder schemas are gone, 401/403/429 are
    attached to every operation and 409 to every idempotent POST, and all 55
    operations that lacked a description have one.
  • Idempotent-Replayed is documented as a response header on idempotent POSTs.
  • /me now correctly advertises environment as live or test.
  • Sidebar navigation is fixed: every guide now resolves to a real category
    instead of rendering a placeholder.

v1.1.0 - 2026-07-17

Clips, highlights, media, streams and catalog write access.

Added

  • Clips: PATCH / DELETE, richer list filters, async autoflip / reframe / crop (202).
  • Highlights: create / update / delete, real POST /highlights/generate (manual | ai | ai_global), progress GET.
  • Media: library filters, /media/stats, /media/filters/counts, GET /media/{id}.
  • Streams: streams:read / streams:write, create / update / end (reads still accept catalog:read).
  • Catalog write: competitions, teams, players, tags (catalog:write).
  • Storage: bulk push + external-storage status.
  • Webhooks: PATCH /webhook-endpoints/{id}.

Changed

  • POST /highlights/generate no longer returns 501; it queues a real job.

v1.0.1 - 2026-07-17

Audit hardening pass.

Fixed

  • POST /clips/generate now creates an HLS clip job with a trackable clip_id.
  • POST /publish with clip_id hydrates media from the clip (optional social_profile_id).
  • GET /media/{id}/signed-urls reports signed + expires_at (signed when GCS key available).
  • clip.created webhook is emitted when clips are created.
  • Key scopes are intersected with parent app scopes; partner webhooks are app-scoped; deleting an app removes its webhooks.

Added

  • Nested GET /streams/{id}/clips and GET /streams/{id}/highlights.
  • Catalog get-by-id: competitions, teams, players, tags.
  • Partner webhook POST .../test and POST .../deliveries/{id}/replay.
  • Error code not_implemented (501).

Changed

  • POST /highlights/generate returns 501 not_implemented. It queues real work as of v1.1.0.

v1.0.0 - 2026-07-01

Initial public release of the Zentag Partner API.

Added

  • Authentication: API key pairs (zt_live_pk/sk_...) with scopes,
    hashed-at-rest secrets, rotation with grace window, and revocation.
  • Clips: GET /clips, GET /clips/{id}, GET /clips/{id}/export-json,
    POST /clips/generate.
  • Highlights: GET /highlights, GET /highlights/{id},
    POST /highlights/generate.
  • Media: GET /media, GET /media/{id}/signed-urls.
  • Publishing: POST /publish, GET /publish/history, GET /publish/{id}.
  • Storage: POST /clips/{id}/push-external-storage, GET /storage/buckets.
  • Catalog: competitions, teams, players, tags, streams.
  • Webhooks: endpoint management, signed deliveries (clip.ready,
    highlight.ready, publish.*, stream.*), delivery log, replay, and test events.
  • Platform: cursor pagination, per-key rate limiting with X-RateLimit-*
    headers, Idempotency-Key support, and a typed error envelope.

Did this page help you?