Skip to content

2026-06-11 — API Versioning & Route Migration

Summary

All CoreAPI routes are now prefixed with /v1. Assets and webhook routes remain at root.


Documentation Status

Item Status
Route migration (/v1) ✅ Documented here
Branch merged to main ❌ Pending
Document updated ❌ Pending

What Changed

A single v1 group is created in main.go and passed to every domain's RegisterRoutes function instead of the raw app. All route registrations inside each domain are unchanged — the /v1 prefix is applied automatically by the group.

Before: GET /streamers GET /caches GET /channels GET /media ...

After: GET /v1/streamers GET /v1/caches GET /v1/channels GET /v1/media ...

Unchanged (root level): GET /assets/ HEAD /assets/ GET /assets/*/presign POST /hooks/tusd


What Was Changed in Code

main.go

// before
service.RegisterRoutes(app)
media.RegisterRoutes(app)

// after
v1 := app.Group("/v1")
service.RegisterRoutes(v1)
media.RegisterRoutes(v1, app)  // media takes root too for assets/hooks

Every domain/*/routes.go

// before
func RegisterRoutes(app *fiber.App) {
    g := app.Group("/streamers")

// after
func RegisterRoutes(r fiber.Router) {
    g := r.Group("/streamers")

domain/media/routes.go — special case

// media takes two params — v1 for API, root for assets and hooks
func RegisterRoutes(r fiber.Router, root fiber.Router) {
    root.Get("/assets/*", h.ServeAsset)   // stays at root
    root.Post("/hooks/tusd", h.TusdHook)  // stays at root
    m := r.Group("/media")                // goes under /v1
    ...
}


Why Assets and Hooks Stay at Root

  • /assets/* — URLs are stored in the DB and embedded in media responses. Moving them would break all stored URLs and video players.
  • /hooks/tusd — called by the tusd container internally. The URL is hardcoded in compose config. Moving it requires updating tusd config too.

Both are infrastructure-level endpoints, not versioned API endpoints.


Rules for Developers

Adding routes to an existing domain

Nothing changes. Add routes inside your existing group as before — they automatically get /v1.

func RegisterRoutes(r fiber.Router) {
    g := r.Group("/my-domain")
    g.Get("/new-endpoint", MyHandler)  // → /v1/my-domain/new-endpoint
}

Adding a new domain

Use fiber.Router not *fiber.App in the signature:

// routes.go
func RegisterRoutes(r fiber.Router) {
    g := r.Group("/my-new-domain")
    g.Get("/", ListHandler)
    g.Post("/", CreateHandler)
}

Register in main.go:

myDomain.RegisterRoutes(v1)

Adding a webhook or file serving route

Register on app directly in main.go, not on v1:

app.Post("/hooks/my-service", myHandler)  // root level

Or follow the media pattern — accept both r and root in RegisterRoutes.


Frontend / Client Impact

Any frontend or client calling the old paths without /v1 will get 404. Update all API calls:

// before
fetch('/api/streamers')

// after
fetch('/api/v1/streamers')

The nginx proxy in PlayoutAdmin maps /api/coreapi:3000/ so the full path becomes: /api/v1/streamers → coreapi:3000/v1/streamers ✓


Verified Routes

All routes confirmed returning non-404 after migration:

Route Status
GET /v1/streamers 200
GET /v1/caches 200
GET /v1/channels 200
GET /v1/playlists 200
GET /v1/media-channels 200
GET /v1/layout-templates 200
GET /v1/locations 500 (spatial DB issue, pre-existing)
GET /v1/venue-types 200
GET /v1/zones 200
GET /v1/devices 401 (auth required, expected)
GET /v1/campaigns 200
GET /v1/media 200
GET /v1/media/status 200

Known Issues

  • GET /v1/locations returns 500 — pre-existing spatial domain DB encoding issue, unrelated to versioning
  • GET /v1/devices returns 401 — auth middleware requires organizationId, expected behavior