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/locationsreturns 500 — pre-existing spatial domain DB encoding issue, unrelated to versioningGET /v1/devicesreturns 401 — auth middleware requiresorganizationId, expected behavior