Promoters Module Integration Guide
Related documentation: Data ownership
KISUM PLATFORM — PROMOTERS MODULE INTEGRATION GUIDE
Section titled “KISUM PLATFORM — PROMOTERS MODULE INTEGRATION GUIDE”Version: 1.0.0
Audience: junior frontend developers, junior backend developers, QA, partners
Status: integration guide
1. Purpose of this document
Section titled “1. Purpose of this document”This document explains how to call the Base Backend correctly in the new architecture.
It covers:
- how auth works from the caller point of view
- how to send
Authorization - how to send
x-org - what sequence to follow
- how to think about errors
- working request examples
- what not to do
This is the “how to integrate” document.
For full architecture, see:
For the API catalog, see:
2. Basic integration idea
Section titled “2. Basic integration idea”You do not log in through Base Backend.
You:
- authenticate through Auth Backend
- obtain a JWT
- choose the active company
- call Base Backend with:
Authorization: Bearer <JWT>x-org: <COMPANY_ID>
Base Backend then:
- validates the JWT
- validates
x-org - resolves effective access from Auth
- checks promoter entitlement (
access.modulesincludes any ofpromoter,promoters,basic_promoter, orbasic_promoters; legacy object shape may still exposebasic) - checks permission
- allows or denies the route
3. Required headers
Section titled “3. Required headers”Every protected request must include:
Authorization: Bearer <JWT_FROM_AUTH_SERVICE>x-org: <COMPANY_ID>Content-Type: application/jsonx-org is the only supported active-company input for tenant-scoped Base requests.
Do not use query parameters or implicit company fallback for runtime access.
3.1 Authorization
Section titled “3.1 Authorization”Must be a token issued by Auth.
3.2 x-org
Section titled “3.2 x-org”Must be the active company ID for the request.
3.3 Content-Type
Section titled “3.3 Content-Type”Usually application/json, unless the route uses uploads.
4. End-to-end caller flow
Section titled “4. End-to-end caller flow”Step 1 — Authenticate with Auth Backend
Section titled “Step 1 — Authenticate with Auth Backend”The user signs in through Auth, not Base.
Result:
- JWT token
- authenticated user session
Step 2 — Resolve who the user is
Section titled “Step 2 — Resolve who the user is”Caller can use Auth-side identity endpoints as needed.
Step 3 — Choose active company
Section titled “Step 3 — Choose active company”Frontend-Kisum-Promoters does not auto-select a company on login when multiple Promoters-eligible tenants exist. After Auth returns a JWT:
- The app loads
/dashboard(normal shell). Branch on unscopedpromoterEligibleCompanies:- 0 eligible → Subscribe overlay (main pane): pick any membership → cookie →
/profile/billing. - 1 eligible → auto-set cookie, scoped init, full app.
- 2+ eligible → Promoters overlay (main pane): pick entitled company → scoped init.
- 0 eligible → Subscribe overlay (main pane): pick any membership → cookie →
- Billing gate: if the active company lacks Promoters entitlement, non-allowlist routes redirect to
/profile/billing. Allowlist:/profile,/profile/companies(+ detail),/profile/billing. Sidebar product nav stays visible but greyed (“Subscribe to unlock”). Profile menu lists all memberships for switching; header switcher lists Promoters-eligible only. - Self-serve company delete (no package): on
/profile/companies,TENANT_SUPERADMINmay delete an empty company shell viaGET|DELETE /api/users/companies/:id(+ delete-preview). Nox-org. See promoters API §Profile companies. - Eligible companies come from one unscoped
GET /users/init(promoterEligibleCompaniesfrom BFF).
That value is then sent on every tenant-scoped BFF call as:
x-org: <COMPANY_ID>Step 4 — Call Base Backend
Section titled “Step 4 — Call Base Backend”Send the business request with:
- JWT
x-org
Step 5 — Base enforces access
Section titled “Step 5 — Base enforces access”Base validates:
- JWT
x-org- effective access from Auth
promotermodule (or package aliases:promoters,basic_promoter,basic_promoters)- permission
4.1 Artists marketplace BFF (Phase 1)
Section titled “4.1 Artists marketplace BFF (Phase 1)”Since 2026-05-27, Promoters exposes /api/artists-network/* as a browser-facing BFF to Backend-Kisum-Artists /api/v1/*.
Since 2026-06-07, legacy GET /api/artists, GET /api/artists/search, and GET /api/artists/:id also proxy to the same Artists directory API (machine BFF, legacy response shapes). Mongo artist master reads are deprecated for those routes.
Callers still use Promoters as the primary origin (:3099) with the same headers:
Authorization: Bearer <JWT_FROM_AUTH_SERVICE>x-org: <COMPANY_ID>Promoters validates access, then forwards Authorization + x-org upstream. Example:
curl -X GET 'http://localhost:3099/api/artists-network/marketplace/artist-availabilities?from=2026-06-01' \ -H 'Authorization: Bearer <JWT>' \ -H 'x-org: <COMPANY_ID>'Maps to Artists GET /api/v1/marketplace/artist-availabilities.
New booking flows should use /api/artists-network/booking-requests, /booking-offers, /bookings — not legacy Mongo /api/offer or /api/avails.
Full path map: Backend-Kisum-Promoters/docs/ARTISTS_NETWORK_BFF.md.
Artist profile bio_short (Postgres)
Section titled “Artist profile bio_short (Postgres)”- Source of truth:
Backend-Kisum-Artistsartist_profiles.bio_shortviaGET|PUT /api/v1/artists/{id}/profile(bio,bioShort,profileJson). - Not legacy Mongo
POST /api/artists/:id/bioorhttps://v1.kisum.ai/biofor Artists-network reads. - Promoters BFF:
POST /api/artists-network/artists/{id}/profile/ensure-short-bio— whenbio_shortis empty, Gemini summarizes existingbio(strict plain-text prompt), then PUTs profile upstream. Requires promoter artist edit permission. Idempotent;?force=trueregenerates. - Long bio formatting: column
artist_profiles.bio_formatted(true|false|null). When nottrue,POST .../profile/ensure-bio-formattedruns once: Gemini outputs semantic HTML intobio, setsbio_formatted=true, stores original plain text inprofile_json.bioSourcePlain. MigrationBackend-Kisum-Artists/migrations/20260602_artist_profiles_bio_formatted.sql. - Promoters UI: artist detail triggers short-bio ensure in the background; Biography tab triggers bio-format ensure when
bio_formattedis not true. Overview showsbioShortonly + link to Biography tab.
Frontend (Frontend-Kisum-Promoters): libs/data.ts calls the BFF for marketplace avails (getAvails) and outbound booking pipeline (getOfferSent, createOffer). Login uses NEXT_PUBLIC_AUTH_BASE_URL, not Promoters /api/auth/*.
4.2 Canonical event identity (Phase 2)
Section titled “4.2 Canonical event identity (Phase 2)”Since 2026-05-27, Promoter events expose a stable UUID for cross-persona references:
| Field | Stored | API alias |
|---|---|---|
publicEventId | Mongo events.publicEventId | — |
promoterEventId | (same value) | Venue JSON contract |
- Set on create (
POST /api/events,POST /api/v2/events). - Returned on event GET/list/PATCH responses (additive — Mongo
_idunchanged). - Legacy rows without the field receive a UUID on first GET-by-id or internal resolve.
Venue linkage: persist promoterEventId = Promoter publicEventId (UUID), not Mongo _id.
Machine resolver (for Venue Phase 3 client):
GET /internal/events/:eventIdX-Internal-API-Key: <PROMOTERS INTERNAL_API_KEY>:eventId accepts publicEventId (preferred) or Mongo _id. Returns minimal event payload or 404.
Full contract: Backend-Kisum-Promoters/docs/PROMOTER_EVENT_ID.md.
4.3 Venues marketplace BFF (Phase 3)
Section titled “4.3 Venues marketplace BFF (Phase 3)”Since 2026-05-27, Promoters exposes /api/venues-network/* as a browser-facing BFF to Backend-Kisum-Venues.
Callers use Promoters (:3099) with promoter JWT + x-org. For venue-operator-scoped reads/writes, also pass venueCompanyId (query/body) or x-venue-org — the BFF forwards that as upstream x-org.
Example marketplace browse:
curl -X GET 'http://localhost:3099/api/venues-network/marketplace/venues/sleeping?q=london' \ -H 'Authorization: Bearer <JWT>' \ -H 'x-org: <PROMOTER_COMPANY_ID>'Example booking create with event linkage:
curl -X POST 'http://localhost:3099/api/venues-network/bookings' \ -H 'Authorization: Bearer <JWT>' \ -H 'x-org: <PROMOTER_COMPANY_ID>' \ -H 'Content-Type: application/json' \ -d '{ "venueCompanyId": "<VENUE_OPERATOR_COMPANY_ID>", "venueId": "<VENUE_ID>", "spaceIds": ["space-1"], "title": "Show night", "startAt": "2026-06-01T18:00:00.000Z", "endAt": "2026-06-01T23:00:00.000Z", "status": "inquiry", "promoterEventId": "<PUBLIC_EVENT_ID_UUID>" }'marketCompanyId is auto-filled from caller x-org when omitted.
Full path map: Backend-Kisum-Promoters/docs/VENUES_NETWORK_BFF.md.
Legacy: Mongo /api/venues master CRUD frozen for new flows — use Venues BFF.
Required Promoters env: VENUE_INTERNAL_BASE_URL + VENUE_INTERNAL_API_KEY (plus Artists env from §4.1).
4.4 Legacy market retirement (Phase 4)
Section titled “4.4 Legacy market retirement (Phase 4)”Since 2026-05-27, writes to local Mongo market routes return HTTP 410 Gone with error.code: legacy_market_write_removed:
| Legacy write prefix | Use instead |
|---|---|
/api/avails/* (POST/PUT/PATCH/DELETE) | /api/artists-network/marketplace/artist-availabilities (browse); artist publishes in Artists |
/api/offer/* | /api/artists-network/booking-requests, /api/artists-network/booking-offers/* |
/api/offer-agreement-templates/* | Artists contracts workflow (when UI lands) |
/api/agencies/* (writes) | /api/artists-network/marketplace/agencies (browse); roster/team reads still legacy GET until sunset |
GET/HEAD on those legacy prefixes still work for compat reads until Sunset: Sat, 01 Nov 2026 00:00:00 GMT (response includes Deprecation: true).
Full audit: Backend-Kisum-Promoters/docs/LEGACY_MARKET_ROUTES.md.
Frontend (Frontend-Kisum-Promoters): offer accept/reject/sent/detail/update/delete, permissions, active package, and agency list browse migrated; avails CRUD, agency roster/team writes, contract templates, and subscription paths remain to migrate. Dashboard shows Booking network panel from artists-network promoter dashboard; Venues → Marketplace tab uses venues-network BFF.
4.5 Promoter CRM ownership (Phase 5)
Section titled “4.5 Promoter CRM ownership (Phase 5)”Since 2026-05-27, Promoter CRM domains stay in Backend-Kisum-Promoters — events, vendors, dashboard aggregates, tasks, approvals, workspace, and Finance orchestration (not source of truth).
| Stays in Promoters | Does not duplicate in Mongo |
|---|---|
/api/events, /api/festivals, /api/event-*, /api/dashboard/* | Artist booking pipeline → /api/artists-network/* |
/api/vendors, /api/tasks, /api/approval, /api/notifications | Venue master + bookings → /api/venues-network/* |
/api/integrations/finance/* (proxy) | Subscriptions/packages → Core |
Full manifest: Backend-Kisum-Promoters/docs/PROMOTER_CRM_OWNERSHIP.md.
Optional BFF extensions (Phase 5): relationships/trust and touring/logistics reads under /api/artists-network/relationships/* and /api/artists-network/bookings/:bookingId/logistics*.
4.6 In-product self-serve billing (FE-Promoter-Phase A.1)
Section titled “4.6 In-product self-serve billing (FE-Promoter-Phase A.1)”Since 2026-05-27, Frontend-Kisum-Promoters ships a self-serve billing surface at /profile/billing that lets a Promoter operator inspect their commercial state and manage their payment method without leaving the product app.
The surface composes three sources of truth — none of which is the product app itself:
| Surface | Endpoint | Owner | Purpose |
|---|---|---|---|
| Current plan + active add-ons | GET /api/companies/active-package | Promoters BFF → Core subscription-summary | Read-only commercial state for x-org |
| Available add-ons catalog | GET /api/catalog/addons?audience=promoter (Next.js proxy → Core /public/addons) | Promoters FE → Core | Public catalog, no auth; same-origin avoids Core CORS on local app origins |
| Add / replace payment method | ${CHECKOUT}/billing/add-card?companyId=…&returnTo=… | System-Kisum-Checkout | Card capture only happens inside Checkout |
| Add add-on | ${CHECKOUT}/billing/upgrade?companyId=…&addonKey=…&returnTo=… | System-Kisum-Checkout → Core POST /internal/companies/{id}/addons | Free add-ons provision immediately; paid add-ons use Xendit + finalize |
| Cancel add-on | POST ${CHECKOUT}/api/billing/upgrade/cancel { companyId, addonKey } | System-Kisum-Checkout → Core | Cross-origin from product app with shared .kisum.io cookie |
Hard rules preserved:
- The Promoter app never mounts
xendit-components-web. All paid flows deep-link toSystem-Kisum-Checkout. - The Promoter cannot switch their base persona package from this surface — that would orphan tenant data. Persona changes (e.g. Promoter → Venue) require platform-staff intervention in
Frontend-Kisum-Admin. - The Promoter can add or cancel add-ons via Checkout (Option A, shipped 2026-06-09):
- Browse → cart → checkout:
/profile/billinguses an in-app add-on cart (session storage per company). Add to cart collects selections; Proceed to Checkout deep-links to${CHECKOUT}/billing/upgrade?addonKeys=ai,finance&…. - Add: Checkout validates membership + catalog, provisions $0 bundles immediately or opens Xendit when total > 0; finalize syncs Core + recurring plan.
- Cancel:
POST /api/billing/upgrade/cancelwith{ companyId, addonKey }.
- Browse → cart → checkout:
The surface is a full self-serve read + mutate experience for add-ons (base package remains read-only).
5. Example request flow
Section titled “5. Example request flow”5.1 Example: list events
Section titled “5.1 Example: list events”Request
Section titled “Request”curl -X GET 'http://localhost:3099/api/events?type=all&page=1&limit=20' -H 'Authorization: Bearer <JWT>' -H 'x-org: <COMPANY_ID>'What must happen
Section titled “What must happen”- token valid
- company exists in user access scope
basicmodule enabled- user has event view permission
Typical outcome
Section titled “Typical outcome”- success →
200 - bad token →
401 - bad company →
400or403 - no access →
403 - access resolution unavailable →
503
6. Common request patterns
Section titled “6. Common request patterns”6.1 GET
Section titled “6.1 GET”curl -X GET 'http://localhost:3099/api/artists' -H 'Authorization: Bearer <JWT>' -H 'x-org: <COMPANY_ID>'6.2 POST JSON
Section titled “6.2 POST JSON”curl -X POST 'http://localhost:3099/api/agencies' -H 'Authorization: Bearer <JWT>' -H 'x-org: <COMPANY_ID>' -H 'Content-Type: application/json' -d '{ "key": "agency-001", "name": "Example Agency" }'6.3 PUT JSON
Section titled “6.3 PUT JSON”curl -X PUT 'http://localhost:3099/api/agencies/{id}' -H 'Authorization: Bearer <JWT>' -H 'x-org: <COMPANY_ID>' -H 'Content-Type: application/json' -d '{ "name": "Updated Agency Name" }'6.4 DELETE
Section titled “6.4 DELETE”curl -X DELETE 'http://localhost:3099/api/agencies/{id}' -H 'Authorization: Bearer <JWT>' -H 'x-org: <COMPANY_ID>'6.5 Multipart upload
Section titled “6.5 Multipart upload”curl -X POST 'http://localhost:3099/api/files/upload' -H 'Authorization: Bearer <JWT>' -H 'x-org: <COMPANY_ID>' -F 'file=@example.pdf'7. What juniors must remember
Section titled “7. What juniors must remember”7.1 Do not log in through Base
Section titled “7.1 Do not log in through Base”Base is not the login service.
7.2 Do not omit x-org
Section titled “7.2 Do not omit x-org”If the route is tenant-scoped, x-org is required.
7.3 Do not guess company
Section titled “7.3 Do not guess company”The UI or calling layer must know the active company and send it explicitly.
7.4 Do not trust frontend UI
Section titled “7.4 Do not trust frontend UI”Just because a button is visible or hidden does not mean the backend will allow the route.
7.5 Do not call deprecated groups
Section titled “7.5 Do not call deprecated groups”Do not use:
- Auth
- Users
- Company Users
- Invitations
- Teams
- Packages
- Permissions
- Role
- Subscription
Those moved to Auth/Core.
8. Error-handling guide
Section titled “8. Error-handling guide”8.0 410 Gone — removed legacy routes
Section titled “8.0 410 Gone — removed legacy routes”Since 2026-05-27, Backend-Kisum-Promoters returns HTTP 410 Gone (not 404) for route trees that moved to Auth or Core:
/api/auth/*→ Auth/api/subscription/*,/api/packages/*→ Core/api/role/*,/api/permissions/*→ Auth (useGET /api/users/permissionson Promoters for compat reads)POST|PUT|PATCH|DELETE /api/companies→ Core (Promoters keeps GET-only company BFF reads)
Response shape:
{ "success": false, "error": { "code": "route_removed", "message": "This route was removed from Backend-Kisum-Promoters.", "owner": "Backend-Kisum-Auth", "hint": "Authenticate via AUTH_BASE_URL (login, refresh, password flows).", "path": "/api/auth/login", "documentation": "docs/DEAD_ROUTES.md" }}Do not retry these against Promoters — migrate the caller to the owning service.
8.0.1 410 Gone — legacy market writes (Phase 4)
Section titled “8.0.1 410 Gone — legacy market writes (Phase 4)”Since 2026-05-27, POST/PUT/PATCH/DELETE on Mongo market routes return 410 with error.code: legacy_market_write_removed:
/api/avails/*/api/offer/*/api/offer-agreement-templates/*/api/agencies/*
Use /api/artists-network/* for new booking and marketplace flows. GET compat reads remain until 2026-11-01. See §4.4 and docs/LEGACY_MARKET_ROUTES.md.
8.1 400 Bad Request
Section titled “8.1 400 Bad Request”Usually means:
- missing
x-org - malformed
x-org - bad request payload
8.2 401 Unauthorized
Section titled “8.2 401 Unauthorized”Usually means:
- missing token
- invalid token
- expired token
- token rejected during validation
8.3 403 Forbidden
Section titled “8.3 403 Forbidden”Usually means:
- membership not valid for
x-org basicmodule missing- permission missing
8.4 503 Service Unavailable
Section titled “8.4 503 Service Unavailable”Usually means:
- Base could not resolve effective access from Auth
- Auth service unavailable
- network failure / timeout between services
8.5 Important rule
Section titled “8.5 Important rule”503 must not become temporary access.
The request must fail closed.
9. Integration anti-patterns
Section titled “9. Integration anti-patterns”Do not do any of the following:
- call
/auth/*on Base - use Base as the source of identity
- use Base as the source of package/subscription truth
- infer access from the JWT alone
- assume company from frontend state without sending
x-org - silently retry with a different company
- bypass permission checks because a user is “probably admin”
- call /auth/me/access using ?companyId=… instead of x-org
10. Example frontend flow
Section titled “10. Example frontend flow”A typical frontend flow should look like this:
- User signs in with Auth
- Frontend stores current JWT securely
- Frontend gets or already knows active company
- Frontend calls Base with JWT +
x-org - If route denied:
- show proper error
- do not guess fallback
- If
503:- show service unavailable / retry flow
- do not fake access
11. Example backend-to-backend flow
Section titled “11. Example backend-to-backend flow”If another internal backend calls Base on behalf of a user:
- obtain or forward a valid Auth-issued JWT
- send explicit
x-org - call Base route
- respect
401/403/503 - do not add local bypass logic
12. Permission mapping examples
Section titled “12. Permission mapping examples”Examples of route-level access expectations:
GET /dashboard→basic.dashboard.viewGET /artists→basic.artist.viewPOST /artists→basic.artist.createPUT /artists/{id}→basic.artist.editDELETE /artists/{id}→basic.artist.deleteGET /events→basic.event.viewPOST /events→basic.event.createPUT /events/{id}→basic.event.editDELETE /events/{id}→basic.event.deleteGET /vendors→basic.vendor.viewPOST /vendors→basic.vendor.createGET /venues→basic.venue.view
These examples are helpful for integration teams even if the exact permission map is maintained elsewhere.
13. Active route families commonly used by integrations
Section titled “13. Active route families commonly used by integrations”Integrations will most often use:
- Agencies
- Agreements
- Ai
- Approval
- Artists
- Avails
- Companies (business metadata only)
- Dashboard
- Events
- Event Expense
- Event Expense - Approval
- Event Income
- Event Ticket
- Files
- Notifications
- Offer
- Tasks
- Vendors
- Venues
Xero — removed from Promoters (2026-06-07); use Backend-Kisum-Finance for OAuth, sync, and invoice PDFs.
For full route group listing, see:
14. Debugging checklist
Section titled “14. Debugging checklist”If a request fails:
Check 1
Section titled “Check 1”Is the token present?
Check 2
Section titled “Check 2”Is the token issued by Auth and still valid?
Check 3
Section titled “Check 3”Is x-org present?
Check 4
Section titled “Check 4”Is x-org the intended company?
Check 5
Section titled “Check 5”Does the user belong to that company?
Check 6
Section titled “Check 6”Does the user have basic module?
Check 7
Section titled “Check 7”Does the user have the specific basic.* permission?
Check 8
Section titled “Check 8”Is Auth reachable for effective access resolution?
15. Final practical rule
Section titled “15. Final practical rule”To call Base successfully, always think:
Auth firstCompany context secondBase business route thirdIf any of those are wrong, the request should fail.