Skip to content

Promoters Module API Specification

Related documentation: Data ownership · Artist Source of Truth Registry (per-endpoint SoT + callers) · Redis GET response cache (global GET middleware + bypass chain)

Version: 1.0.0
Audience: backend developers, frontend developers, QA, integrators
Status: API specification


This document is the Promoters module API catalog.

It focuses on:

  • route groups
  • request headers
  • common request/response expectations
  • endpoint families
  • deprecation visibility for old groups

It intentionally avoids long architectural explanations.
For architecture and access rules, see:

For integration examples, see:


  • https://api-v2.kisum.dev/api — Staging
  • http://localhost:3099/api — Local

Authorization: Bearer <JWT_FROM_AUTH_SERVICE>
x-org: <COMPANY_ID>
Content-Type: application/json
  • Authorization must contain a JWT issued by Auth Backend
  • x-org must contain the active company context
  • tenant-scoped routes must reject missing or malformed x-org

Inbound Promoters INTERNAL_API_KEY is accepted on the same headers as other Base machine routes: X-Internal-API-Key, x-api-key, or Authorization: Bearer <INTERNAL_API_KEY>.

Routex-org for machine callersUpstream
GET /internal/events/:eventIdRequired when resolving tenant contextPromoters Mongo
GET /newsOptional (global feed)PUBLIC_FUNCTION_URL_NEWS (default https://api-v3.kisum.dev/api/news) — internal key forwarded as x-auth / Authorization

  • application/json for most requests
  • multipart/form-data for uploads and attachments
  • application/xml may appear in legacy Swagger definitions, but JSON is the primary format

  • 200 / 201 / default — success
  • 400 — invalid request / missing malformed x-org
  • 401 — missing or invalid JWT
  • 403 — authenticated but not allowed
  • 500 — internal server error
  • 503 — effective access cannot be resolved from Auth

These are the active business-domain groups in Base Backend.

  • Agencies — 14 operations
  • Agreements — 10 operations
  • Ai — 6 operations
  • Analytics — 1 operation
  • Approval — 6 operations
  • Artists — 27 operations
  • Avails — 9 operations
  • Cash Flow — 1 operation
  • Countries — 9 operations
  • Companies — 11 operations (business metadata only — not membership/access)
  • Profile companies (membership-scoped) — 4 operations (GET/DELETE /users/companies*, no x-org)
  • Dashboard — 3 operations
  • Events — 26 operations
  • Event Expense — 17 operations (estimated only; actual expenses blocked)
  • Event Expense - Approval — 7 operations (source of truth for actual expenses in this service scope)
  • Event Group — 10 operations
  • Event Income — 14 operations
  • Event Tax — 6 operations
  • Event Ticket — 18 operations
  • Event Intelligence Copilot — 3 operations
  • Kisum — 7 operations (legacy /kisum/* aliases)
  • Integrations - Finance — 11 operations
  • Exchange — 2 operations
  • Festivals — 6 operations
  • Files — 1 operation
  • Genres — 1 operation
  • Integrations — 2 operations
  • News — 1 operation
  • Nextcloud — 3 operations
  • Notifications — 6 operations
  • Offer — 16 operations
  • Rankings — 21 operations (5 legacy + 16 Kworb read APIs)
  • Reviews — 1 operation
  • Roster — 6 operations
  • Regions — 3 operations
  • Setlistfm — 8 operations
  • Tasks — 8 operations
  • Transactions — 2 operations
  • Trend — 1 operation
  • Vendors — 11 operations
  • Vendors System — 15 operations
  • Venues — 19 operations
  • Untagged — 2 operations (BLOCKER — must be categorized before production release)

These groups must not be used in Base Backend anymore.

  • Auth — ❌ deprecated; moved to Auth Backend
  • Company Users — ❌ deprecated; moved to Auth Backend
  • Company User Invitations — ❌ deprecated; moved to Auth Backend
  • Company Teams — ❌ deprecated; moved to Auth Backend
  • Packages — ❌ deprecated; moved to Platform Core Backend
  • Permissions — ❌ deprecated; moved to Auth Backend
  • Role — ❌ deprecated; moved to Auth Backend
  • Subscription — ❌ deprecated; moved to Platform Core Backend
  • Users — ❌ deprecated; moved to Auth Backend


This section is intentionally high-level.
For junior developers, the goal is to understand what kind of routes live in each group.

Typical responsibilities:

  • list agencies
  • create agency
  • update agency
  • delete agency
  • manage agency roster
  • manage agency team members

Typical routes:

  • GET /agencies
  • POST /agencies
  • GET /agencies/search
  • GET /agencies/{id}
  • PUT /agencies/{id}
  • DELETE /agencies/{id}
  • GET /agencies/{id}/roster
  • POST /agencies/{id}/roster
  • DELETE /agencies/{id}/roster/{rosterId}
  • GET /agencies/{id}/team
  • POST /agencies/{id}/team
  • PUT /agencies/{id}/team/{teamId}
  • DELETE /agencies/{id}/team/{teamId}

Typical responsibilities:

  • retrieve agreements
  • create agreement assets
  • generate or delete agreement PDFs

Typical routes:

  • GET /agreements
  • GET /agreements/{id}
  • PUT /agreements/{id}
  • GET /agreements/{id}/{assets}
  • POST /agreements/{id}/{assets}
  • PUT /agreements/{id}/{assets}
  • GET /agreements/{id}/{assets}/{extra}
  • POST /agreements/create
  • POST /agreements/create/pdf/{offerId}
  • DELETE /agreements/delete/pdf/{offerId}

Typical responsibilities:

  • AI assistant
  • Gemini route
  • AI conversation/chat routes

Typical routes:

  • POST /ai-kisum/gemini
  • POST /ai-kisum
  • POST /ai-kisum/n8n
  • GET /ai-kisum/conversation/{id}
  • POST /ai-kisum/conversation
  • POST /ai-kisum/chat

Typical responsibilities:

  • analytics views exposed by Base

Typical routes:

  • GET /analytics/financials

Typical responsibilities:

  • approval queue
  • approve/reject/cancel actions
  • approval views by user

Typical routes:

  • GET /approval
  • GET /approval/by-user
  • POST /approval/{id}/approve
  • POST /approval/{id}/reject
  • POST /approval/{id}/cancel
  • GET /approval/{id}

Full per-endpoint matrix (source of truth, Promoters handler, upstream path, Mongo stores, frontend callers, UI screens): Artist Source of Truth Registry. Typical responsibilities:

  • artist CRUD
  • discovery/search
  • stats
  • demographics
  • prediction
  • historical data
  • platforms
  • events
  • contacts
  • bios

Typical route families:

Artist directory (master data — Artists Postgres, 2026-06-07):

RouteSource of truthPromoters behavior
GET /artistsBackend-Kisum-Artists GET /api/v1/artistsMachine BFF after JWT/x-org; maps to legacy { data, pagination }
GET /artists/searchsame upstream (q)Legacy search array shape
GET /artists/{id}same upstream (:id + include=profile,genres,platforms,providers)bigint id, uuid, or legacy Mongo gid ref
GET /v2/artistssame as GET /artistsDelegates to upstream (Mongo layer models removed)
GET /v2/artists/{id}same as GET /artists/{id}Delegates to upstream

Requires ARTISTS_INTERNAL_BASE_URL + ARTISTS_INTERNAL_API_KEY on Promoters (e.g. http://artists.local.kisum.io:3831). Not Mongo kisum_data.artists_v2 for any artist master read.

Source split (2026-06-07): Profile routes (GET /artists, /search, /{id}, /v2/artists*) and event lineup display fields → Artists upstream. Stats/analytics (/stats, /demographics, discography, Soundcharts/Viberate/Songstats/Spotify, predictions) → Mongo kisum_data.artists_v2 + data_analyst.artist_data (unchanged). Event lineup stats still load from Mongo via getStatsById.

  • POST /artists — (legacy write surface; directory creates belong in Artists long-term)
  • GET /artists/{id}/discographyDefault: artist Spotify albums list + top tracks (unchanged). With ?ids=id1,id2: batch album details (max 50); proxies MusicData GET /spotify/albums/multiple (fallback: Promoters spotify.getAlbums). Response data: { artistId, albums[], source } — full album objects include popularity + label. Albums must belong to artist when Spotify id is known (a non-owned id 404s the whole batch). Frontend Discography tab calls the default list, then re-fetches the artist’s own album-type ids via ?ids= (chunked, fault-tolerant) to read popularity for the popularity-ranked Top Albums bento.
  • GET /artists/{id}/discography/{albumId} — Single album detail; proxies MusicData GET /spotify/albums/{albumId} (fallback: Promoters spotify.getAlbum). Response data: { artistId, albumId, album, source }. HTTP cache: global Promoters GET middleware; bypass chains to MusicData via x-redis: bypass.
  • GET /artists/{id}/top-tracks — Kworb all-time top 10 songs for the artist (charts.kworb_spotify_top_artists.tracks), enriched with Spotify album art/metadata via MusicData GET /spotify/tracks/multiple (fallback: Promoters Spotify client when MUSICDATA_* unset). HTTP cache: global Promoters GET middleware (promoters: prefix, 7d TTL) — no per-route Redis keys. Bypass: ?live=true, x-redis: bypass, or frontend NEXT_PUBLIC_REDIS_BYPASS=true; Promoters forwards x-redis: bypass to MusicData on internal calls. Response data.tracks[]: rank, title, spotifyId, link, streams, daily, image, albumName, artists, popularity.
  • GET /artists/{id}/top-songs-regional — NetEase + QQ Music hot top 10 songs when the artist has platform IDs (platforms.netease.id / platforms.qq.id, fallback streamingPlatforms.netease / streamingPlatforms.qq). Skips a platform when its ID is missing. Proxies MusicData GET /netease/artist/top/song?id={neteaseId} and GET /qq/artist/song?singermid={qqId} (requires MUSICDATA_BASE_URL + MUSICDATA_INTERNAL_API_KEY; returns empty tracks with source: unavailable when unset). HTTP cache: same global GET middleware on Promoters and MusicData when bypass is not active. Response data: { artistId, netease, qq } where each section is { platform, platformId, tracks[], source, cached, cachedAt } and each track has rank, title, platform, platformTrackId, albumName, image, artists, popularity, link. Frontend artist overview Top Songs on NetEase / Top Songs on QQ Music sections (below Spotify Top Songs).
  • GET /artists/{id}/chart-presence — Local read against Mongo charts Kworb collections (utils/kworb-chart-presence.utils.js). Resolves artist id / name / platforms.spotify.id; returns data.charts[] grouped by chart family. Same match semantics as MusicData analyst route (no MUSICDATA_* required). Frontend artist overview Chart Presence section.
  • GET /artists/{id}/top-countries — Soundcharts social followers countryPlots via GET /api/v2.37/artist/{uuid}/social/{platform}/followers/ (default instagram; optional platform=tiktok|youtube). Requires SOUNDCHARTS_APP_ID + SOUNDCHARTS_API_KEY; resolves data_platforms.soundcharts_uuid from Spotify when missing. Query: limit (default 10, max 50), optional startDate / endDate. Response data: { artistId, platform, soundchartsUuid, countries[], totalFollowers, snapshotDate, source } where each country has rank, countryName, countryCode, followers, share (% of snapshot total). HTTP cache: global Promoters GET middleware; bypass chains per Redis GET cache. Frontend artist overview Top Countries section (flag list + share bars, audience by Instagram followers); country flags via flagcdn.com.
  • GET /artists/{id}/charts/song/ranks/{platform} — Soundcharts proxy for Get chart song entries: GET /api/v2/artist/{uuid}/charts/song/ranks/{platform}. Path platform = Soundcharts song-chart platform code (e.g. spotify). Query passthrough: currentOnly (0|1), offset, limit (max 100), sortBy (position|rankDate), sortOrder (asc|desc); includeItems=false (or 0) omits items[] from the Promoters response (aggregate info still computed from the upstream page). Requires SOUNDCHARTS_APP_ID + SOUNDCHARTS_API_KEY; resolves data_platforms.soundcharts_uuid from Spotify when missing (may persist on Mongo artists_v2). Response data: { artistId, platform, soundchartsUuid, source, related, items[], info, page, errors[] } where each items[] entry includes chart metadata, position, peakPosition, current, song, etc. (Soundcharts shape). info (Promoters aggregate over returned items[]): { peakPosition, timeonChart[] }peakPosition = best (lowest) peakPosition across items; timeonChart = per chart.frequency (daily / weekly / monthly when present) average of timeOnChart as { frequency, time }. HTTP cache: global Promoters GET middleware.
  • GET /artists/{id}/popularity/{platform}MusicData analyst proxy (GET /analyst/artists/{id}/popularity?platform=…). Path platform = Soundcharts platform code (spotify, instagram, apple-music, …). Requires MUSICDATA_INTERNAL_BASE_URL (or MUSICDATA_BASE_URL) + MUSICDATA_INTERNAL_API_KEY; returns source: unavailable / error: musicdata_not_configured when unset. Optional query: refresh, startDate, endDate (passed through to MusicData Mongo read filters / forced refresh). MusicData owns Mongo data_analyst.artist_popularity and conditional Soundcharts backfill (60d rule). Promoters summarizes MusicData items[] into yearly[] / peak / current via utils/artist-popularity.utils.js. Response data: { artistId, platform, soundchartsUuid, source: musicdata_analyst, peak, current, yearly[], lastCrawlDate, errors[], musicData?: { dataSource, soundchartsApiCalls, refreshed, soundchartsFetch } }yearly[] = { year, average, count } per calendar year (0-valued points excluded as gaps); peak/current = { value, date }. Powers the Rankings → Historical Insights trajectory chart. HTTP cache: global Promoters GET middleware. See MusicData analyst.
  • GET /rankings/spotify/listeners?artistId={id} — single-artist branch (beside ?spotifyId=): returns the one Kworb listeners doc matched by Kisum artist_id (peakListeners, peakRank, listeners). Powers Rankings → Historical Insights “Peak Monthly Listeners”.
  • GET /artists/{id}/stats
  • GET /artists/{id}/demographics
  • GET /artists/{id}/events
  • GET /artists/{id}/bandsintown/events — external artist concerts; failover order: Bandsintown → Soundcharts GET /v2/artist/{uuid}/events → Last.fm artist.getPastEvents / artist.getEvents (env: LASTFM_API_KEY). Returns [] when all sources fail (not 500). Optional ?date=all|past|upcoming. Items may include source: bandsintown | soundcharts | lastfm. Enrichment sets location.*, copies venue.country, and server-side geocodes missing venue.latitude / venue.longitude via GOOGLE_MAPS_API_KEY (country-centroid fallback from Mongo countries).

Stats & demographics data sources (2026-06-06):

RouteStorage (SoT)Refresh triggerExternal providers (on refresh only)
GET /artists/{id}/statsMongo data_analyst.artist_data keyed by artist_id (Kisum int32 kisum_data.artists_v2.id)On-demand when row missing, updatedAt older than 7 days, or ?bypass_cache=true / ?refresh=true. No crontab (2M+ artists).MusicData analyst (when hybrid env set): Viberate → Songstats → Soundcharts → native MusicData platform gap-fill (genius, lastfm, netease, qq, Spotify/Deezer/YouTube/Instagram/TikTok/Bandsintown). Local fallback: tiers 1–3 only via artist-stats-refresh.utils.js (no native tier duplication). Refresh failure → return stale artist_data if present.
GET /artists/{id}/demographics(unchanged) Soundcharts + fallbacksPer existing demographics pipelineSee prior demographics row

Promoters read path: Mongo artist_data if fresh (7-day rule) → else on-demand refresh. No handler-level Redis on stats; optional GET response cache via global middleware only. Auth /auth/me/access is always live (never cached in Promoters).

MusicData analyst writer (Phase 2+): When MUSICDATA_BASE_URL + MUSICDATA_INTERNAL_API_KEY are set on Promoters, stale/missing refresh is delegated to GET /analyst/artists/{artistId}/stats on Backend-Kisum-MusicData (sole writer to artist_data). Promoters keeps hybrid fast path (reads Mongo when fresh). See MusicData analyst module.

Local fallback (no MusicData env): Promoters writes artist_data via utils/artist-stats-refresh.utils.js + utils/artist-data-stats.utils.js.

Requires SOUNDCHARTS_APP_ID, SOUNDCHARTS_API_KEY, optional SOUNDCHARTS_BASE_URL. Public stats response shape unchanged (flat keys: spotify, youtube_views, …).

  • POST /artists/{id}/bio
  • POST /artists/{id}/prediction-v2
  • POST /artists/{id}/prediction-v3

Typical responsibilities:

  • avails list
  • avails by user
  • avails-linked shows

Typical route families:

  • GET /avails
  • POST /avails
  • GET /avails/user
  • GET /avails/{id}/shows
  • GET /avails/{id}/shows-summary

Data ownership: Chart snapshots live in MongoDB data_analyst (kworb_* collections + legacy spotify_top_streamed_*, rank_vibrate). Scrapers run outside Promoters (Kworb cron); Promoters is read-only.

Auth: Bearer JWT + x-org on all routes.

Kworb response shape (in data):

{ "meta": { "scrapedAt": "..." }, "rows": [], "pagination": { "page": 1, "limit": 50, "total": 0 } }

Legacy routes (unchanged):

  • GET /rankings/alltime — all-time streamed artists (Kisum enrich)
  • GET /rankings/cityranking — city listener rankings (rankings, region query)
  • GET /rankings/topartists — top artists list
  • GET /rankings/topsong — top songs by year (rankings = year, including all)
  • GET /rankings/worldwideViberate worldwide chart (not Kworb)

Kworb routes (2026-06-03):

  • GET /rankings/current-charts — multi-platform #1 grid
  • GET /rankings/global-artists?list=main|extended — Kworb composite leaderboard (use instead of worldwide for Kworb data)
  • GET /rankings/itunes/charts/{countryCode} — top ~200 songs per country
  • GET /rankings/itunes/worldwide/{chart}ww | europe
  • GET /rankings/itunes/pop/{countryCode} — live popularity bars
  • GET /rankings/itunes/sales/us — US digital sales estimates
  • GET /rankings/itunes/countries — market catalog
  • GET /rankings/youtube/trending, GET /rankings/youtube/videos-24h
  • GET /rankings/radio/us
  • GET /rankings/spotify/listeners — optional spotifyId
  • GET /rankings/spotify/songs?period= — not all (legacy topsong for all-time)
  • GET /rankings/spotify/albums?period=all|least_streamed_track
  • GET /rankings/spotify/country-chartscountry, chartType, variant
  • GET /rankings/spotify/countries
  • GET /rankings/scrape-runs — cron health (job, limit)

Billboard routes (2026-06-08): Mongo charts.billboard snapshots (synced by MusicData /crontab/billboard).

  • GET /rankings/billboard/categories — chart list built from snapshot documents (weekly-snapshot / year-end-snapshot, one row per distinct chart+meta), merged with optional weekly-categories / year-end-categories labels. Optional ?cat= filter. Rows: id (slug), label, chart, meta, category, year.
  • GET /rankings/billboard/chart — latest snapshot for chart=weekly|year-end + meta (e.g. hot-100). Optional weekOf, year. Normalizes data[].artist[] → flat artist label + artist_id (first linked Kisum id) for row click / insight rail.

Canonical field reference: Backend-Kisum-Promoters/rankings/RANKING.md.

Frontend (Frontend-Kisum-Promoters, 2026-06-08): /market_analysis/rankings adds a Billboard provider tab (/market_analysis/rankings/billboard/{chart-slug}). Chart list loads from billboard/categories; table uses billboard/chart with infinite scroll. Row click uses artist_id from data[].artist[].id when present. Legacy + Kworb tabs unchanged (Overview, iTunes, YouTube/Radio, Spotify, Ops).

The same pattern continues for:

  • Cash Flow
  • Countries
  • Companies
  • Dashboard
  • Events
  • Event Expense
  • Event Expense - Approval
  • Event Group
  • Event Income
  • Event Tax
  • Event Ticket
  • Event Intelligence Copilot
  • Kisum
  • Integrations - Finance
  • Exchange
  • Festivals
  • Files
  • Genres
  • Integrations
  • News
  • Nextcloud
  • Notifications
  • Offer
  • Rankings
  • Reviews
  • Roster
  • Regions
  • Setlistfm
  • Tasks
  • Transactions
  • Trend
  • Vendors
  • Vendors System
  • Venues

Each of those route groups remains active in Base unless explicitly marked deprecated.

Removed (2026-06-07): /api/xero/* and /api/integrations/xero/* — Xero is owned by Backend-Kisum-Finance, not Promoters.

Browser profile flows under /profile/companies. Do not send x-org. No Promoters module entitlement required.

MethodPathWhoPurpose
GET/users/companiesAuthenticated memberList membership companies; rows include package, packageName, packages[], membership.role from Auth + Core subscription summary
GET/users/companies/:idMember of companyProfile company detail (Core snapshot, TCompany-compatible shape)
GET/users/companies/:id/delete-previewTENANT_SUPERADMIN, no packageImpact preview: memberships removed vs users deactivated; actorWillBeDeactivated for caller
DELETE/users/companies/:idTENANT_SUPERADMIN, no packageRetire company: Auth membership/user cleanup → Core status: inactive → Promoters Mongo tenant purge

Delete rules

  • Only companies with empty commercial entitlements (no base package, no add-ons).
  • Members with other active companies: membership removed for this company only; account stays active.
  • Members with only this company: membership removed + Auth user soft-deactivated (sessions revoked).
  • Deleter with other companies stays logged in; sole-company deleter is signed out client-side after success.

Upstream (machine, BFF only — not browser)

  • Auth: GET /internal/admin/companies/{id}/memberships, GET /internal/admin/users/{id}/companies, POST /internal/admin/companies/{id}/memberships (isActive: false), DELETE /internal/admin/users/{id}
  • Core: PATCH /internal/companies/{id} { "status": "inactive" }

Design spec: Frontend-Kisum-Promoters/docs/superpowers/specs/2026-06-07-company-delete-no-package-design.md.


Terminal window
curl -X GET 'http://localhost:3099/api/events?type=all&page=1&limit=20' -H 'Authorization: Bearer <JWT>' -H 'x-org: <COMPANY_ID>'
Terminal window
curl -X POST 'http://localhost:3099/api/artists/{id}/bio' -H 'Authorization: Bearer <JWT>' -H 'x-org: <COMPANY_ID>' -H 'Content-Type: application/json' -d '{
"resJson": {}
}'
Terminal window
curl -X POST 'http://localhost:3099/api/files/upload' -H 'Authorization: Bearer <JWT>' -H 'x-org: <COMPANY_ID>' -F 'file=@example.pdf'

  • all protected routes require Authorization
  • all tenant-scoped routes require x-org
  • deprecated groups must not be used
  • Untagged routes must be categorized before release
  • active business routes should map to basic.* permissions as defined by architecture

Deprecated. Moved to Auth Backend.

Deprecated. Moved to Auth Backend.

Deprecated. Moved to Auth Backend.

Deprecated. Moved to Auth Backend.

Deprecated. Moved to Platform Core Backend.

Deprecated. Moved to Auth Backend.

Deprecated. Moved to Auth Backend.

Deprecated. Moved to Platform Core Backend.

Deprecated. Moved to Auth Backend.