Artist Module Backend
Related documentation: Artist Source of Truth Registry · Artist Product Vision · Artist Endpoint Map · Frontend Artist · Artist Module Backend API · Base Modules Backend · Data ownership
Artists accepts Auth-issued X-Service-Token credentials additively and can use them for Core and Venues calls behind S2S_TOKENS_ENABLED; see the canonical service-token contract.
Purpose
Section titled “Purpose”This page explains what the artist base module is in Kisum today.
The current runtime repository is Backend-Kisum-Artists, and in product taxonomy this backend is the Artist module backend.
This documentation should be read as a description of the current system.
What the Artist module is
Section titled “What the Artist module is”The Artist module is Kisum’s artist/agency commercial network and booking backend.
Official marketplace availability enters the normal agency booking pipeline (booking_offers) through approved agency/Core ownership. Secondary inventory enters the separate Middle Agent deal desk (middle_agent_offers). The neutral marketplace API dispatches between them after validating the listing; it does not make Exclusive agencies depend on Middle Agent operators or entitlements.
It is where the platform handles:
- artist and agency directory records
- representation and roster ownership
- availability management
- booking negotiation and booking confirmation
- booking-linked contracts
- touring and itinerary coordination
- logistics submissions and artist-side approvals
- relationship memory and trust
- promoter and agency read models
- booking-linked finance visibility
Short version:
- this is not just artist CRUD
- this is not just a public marketplace
- this is the operational artist/agency side of the live-entertainment network inside Kisum
What the Artist module owns
Section titled “What the Artist module owns”The Artist module runtime is the source of truth for:
- artists
- artist profiles
- market companies
- people / contacts
- genres / subgenres
- Market geo reference data
- platform accounts
- provider references
- roster links
- artist team assignments
- company claims
- artist representation claims and disputes
- artist availabilities
- booking requests / offers / holds / bookings / booking activity
- contract templates and booking-linked contract workflow
- tours, stops, routing, itinerary, logistics, and logistics approvals
- relationship memory, notes, flags, ratings, trust scoring
- dashboards, shortlists, and marketplace read models
- header notifications (
artist_notifications,GET/POST /api/v1/notifications) — persisted inbox/task rows for the Artists app bell;?sync=1refreshes from booking inbox summary + agency tasks; workflow mutations triggerSyncPending - booking-level finance visibility and finance handoff audit
What the Artist module does not own
Section titled “What the Artist module does not own”- users
- sessions
- JWT
- memberships
- permission truth
- tenant companies
- business units
- subscriptions
- packages
- add-ons
- entitlement truth
Finance
Section titled “Finance”- deposits as source of truth
- invoices
- settlements
- payouts
- accounting / ledger truth
- venue inventory
- space operations
- venue execution workflows
- venue-owned contract truth
Artists reads venue master data (read-only) to power the Artists app’s venue directory: GET /api/v1/venues and GET /api/v1/venues/:id proxy the canonical Venues admin feed (/internal/admin/venues[/:id]) via the internal/artists/venues package + the shared middleagent VenuesClient, authenticated with the s2s venues authorizer / VENUE_INTERNAL_*. It never writes venues and exposes no marketplace/booking/spaces/availability. See Artist Endpoint Map.
Artists also reads promoter tenant data (read-only, 2026-07-15) to power the Artists app’s Promoters directory — the platform’s first cross-tenant data disclosure. GET /api/v1/promoters[/:id[/shows]] proxies Backend-Kisum-Promoters’ aggregated internal feed (/internal/directory/promoters*, itself backed by Core identity + Auth staff + Promoters’ own events + Artists/Venues name resolution) via a new internal/artists/promoters package (PromotersClient, s2s audience promoters). Artists never writes promoter, company, or user data through this path. Staff exposure is name + company role + department + work email only — never phone. See Artist Endpoint Map and Data ownership.
Runtime shape
Section titled “Runtime shape”- runtime: Go + Fiber
- route base:
/api/v1 - source of truth: Market Postgres (
artists_datain production;artists_demowhenAPP_ENV=development) - docs artifact: generated OpenAPI plus hand-written system docs
- directory name search: trigger-maintained
artists.normalized_name+ GIN trigram index; search predicates must reference the indexed column directly
Database env selection (API runtime only)
Section titled “Database env selection (API runtime only)”Backend-Kisum-Artists keeps two Postgres URLs in .env. Only make run / go run ./cmd (the API server) picks from APP_ENV:
APP_ENV | DSN env var | Purpose |
|---|---|---|
development | DEMO_DATABASE_URL | Demo DB (artists_demo) — fictional seed data for local dev / App Store screenshots |
production (default for deploy; also used for staging and other non-development values) | DATABASE_URL (fallback DB_URL) | Production artist-directory SoT (artists_data) |
APP_ENV wins over a manually set DATABASE_URL in development for the API only.
Migrations (make migrate-*, go run ./cmd/migrate) and other DB maintenance always use DATABASE_URL (production), regardless of APP_ENV.
Contract document generation (2026-07-16)
Section titled “Contract document generation (2026-07-16)”Booking contracts render as auto-filled Word documents. POST /api/v1/contracts/{id}/generate (also run automatically on contract create) fills the contract’s template with the booking’s full deal data and records the result as a contract version (render_* columns; stored under files/artists/contracts/prod/).
- Template resolution: agency-uploaded DOCX source (S3) when the contract references one; otherwise the embedded platform default (repo
/contracts, go:embed) — exclusive vs middle-agent agreement chosen by booking channel.templateId: "default"on create stores NULL and selects the default.GET /contract-templates/default/sourceserves the default for download. - Engine:
<<PLACEHOLDER>>markers, resilient to Word splitting markers across formatting runs; repeated markers take per-occurrence values (payment-schedule rows). Template uploads derive the marker manifest intovariable_manifest_json(non-DOCX rejected). - Auto-filled: parties (artist, owner company = contracting company & tax/income recipient, buyer, agency), dates, venue/city/country, event, capacity, set length, fee + withholding/landed treatment, structured payment plan, key-detail cost responsibilities, resolved deal clauses (Special Conditions), template-stated defaults.
- Deliberately left as markers (returned as
unresolvedPlaceholders, persisted in version notes — the complete-before-signature checklist): registration numbers, addresses, tax IDs, bank details, signatories, purchaser contacts, rider file/version. Follow-ups tracked in the repo TODO: company legal profile fields, artist rider storage (rider must currently be attached manually via contract attachments).
Contract data collection (2026-07-17, Phase A)
Section titled “Contract data collection (2026-07-17, Phase A)”Two of the checklist gaps above now have a real data source. Full design and rollout phases: modules/docs/superpowers/specs/2026-07-17-contract-data-collection-design.md.
- Migration
000044(USER-RUN, not yet applied) addsartist_availabilities.contract_terms_json,companies.legal_profile_json,booking_offers.mutual_terms_acceptance_json(all nullable JSONB). GET/PATCH /api/v1/companies/:idreads/writeslegalProfileJson(address, representative name, 6 bank fields) — fillsAGENCY_COMPANY_ADDRESS,AGENCY_REP_NAME(fallback: generating user), and theAGENCY_BANK_*markers.- Availability create/update accept
contractTerms(contracting company name/address, production responsibility, tech-production, comps, travel party) — fillsARTIST_COMPANY_NAME/_ADDRESS; empty ⇒ falls back to the agency’s own legal profile (“the agency signs”). Partial updates never wipe existing fields (COALESCE); an explicit empty object clears them. - Decision: the artist contracting company is chosen per availability/listing (it can change deal-by-deal) and re-confirmed at contract issuance; the agency is always the fallback signer.
- Phase A of 4 — see Phase C below for the next shipped phase; Phase D (signer-based signatory fill) is still pending.
Phase C — Mutual-terms acceptance (2026-07-17)
Section titled “Phase C — Mutual-terms acceptance (2026-07-17)”POST /api/v1/booking-offers/{offerId}/mutual-terms/accept— body{ terms: [{ key, value, accepted: true }] }, same agency-side auth as accept. Persists a snapshot{ acceptedByUserId, acceptedByOrgId, acceptedAt, terms }intobooking_offers.mutual_terms_acceptance_json. Offer detail now exposes top-levelmutualTerms(proposed) andmutualTermsAcceptance(persisted snapshot).- Accept gating: accepting an EXCLUSIVE offer whose offersheet carries
mutualTermsrequires a complete, value-bound acceptance already on file — every proposed term key AND its exact current value, including each cancellation-ladder band (ladder:<band>bound to that band’s current charge). Missing, stale, or incomplete → 422MUTUAL_TERMS_NOT_ACCEPTED(message starts “Mutual terms”). An unrecognized-but-presentmutualTermsshape fails closed rather than skipping the gate. Offers with nomutualTermsskip the gate entirely (back-compat). - Any counter or buyer revision NULLs the existing acceptance — re-acceptance is required after every new offer version. This value-bound, reset-on-any-version behavior is a permanent decision, never key-only.
- Copy-on-accept: on the actual accepted flip (never on an idempotent re-accept), the availability’s
contract_terms_jsonbest-effort snapshots into the accepted version’soffersheet.artistContractTerms; never fails accept. - Frontend:
MutualTermsDialog(per-term explicit accept; Accept-terms disabled until all checked; “Propose different terms” routes to counter); auto-opens on accept when acceptance is missing and reactively on the 422; offers predating structured terms show platform defaults with a note; offer detail gains a read-only “Contract terms” card (accepted/pending badges + Review & accept). - Notifications (best-effort SES): agency offer-arrival email gains a “terms awaiting your acceptance” line when
mutualTermsis present; acceptance emails the buyer signatory a “Contract terms accepted” summary. - Phase C of 4 (A/B shipped same day).
Per-show engagement/billing (2026-07-17, same day)
Section titled “Per-show engagement/billing (2026-07-17, same day)”Promoters moved engagement fields off a single top-level offersheet engagement section onto each shows[i] (engagementType, agePolicy, doors, curfew, billing) and reshaped commercialDeals (sponsors/salesReport booleans, merchandising: {artistPct, promoterPct, notes?}, ticketScaling rows). This repo followed:
- Generator (
internal/artists/contracts/rendervalues.go):ENGAGEMENT_TYPE/AGE_POLICY/DOORS/CURFEW/BILLINGnow read from the booking’s own show row;SPONSORSrenders YES/NO;MERCHANDISINGrenders “Artist X% / Promoter Y%” (+ notes);TICKET_SCALLINGrenders one line per ticket-scaling row. - Mutual-terms acceptance keys: billing is accepted per show —
billing:<showIndex>replaces the singlemutualTerms.billingkey from Phase C above (one accept row per show inMutualTermsDialog, labelled by that show’s date/venue). This is a permanent key-format decision. - Audit result: with a fully-populated deal, the generator now leaves only
SIGNATURE_DATEunresolved. - Planned Phase D (signer-based
COMPANY_SIGNATORY_NAMEfill) is OBSOLETE and will not be built — the product owner’s v3 template (72 markers) no longer contains that marker; the signature block usesBUYER_SIGNATORY_*+SIGNATURE_DATEonly.
File storage (S3) — contract files (2026-07-16)
Section titled “File storage (S3) — contract files (2026-07-16)”Contract files upload to AWS S3 bucket kisum.io and are addressed publicly as https://files.kisum.io/<key>. The key layout is a platform contract (locked by internal/artists/contracts/storage_test.go):
| Content | Key prefix |
|---|---|
Contract template sources (DOCX, uploaded from /contract-templates/[id] in the Artists app) | files/artists/contracts/templates/ |
| Real contract artifacts (attachments, signed artifacts, future rendered contracts) | files/artists/contracts/prod/ |
Rules that must not regress:
AWS_S3_ENDPOINT(=files.kisum.io) is the public files domain used only to build stored URLs — never the S3 API endpoint. The API endpoint is always the AWS regional default, same as the Admin/Core/Venues uploaders. (Feeding the CDN host to the SDK is what silently broke all uploads before 2026-07-16.)AWS_S3_PREFIXstays empty in production — it only segregates non-prod environments (e.g.dev/files/artists/...).- Uploads fail closed: unconfigured storage returns an error; it never fabricates success.
- Contract objects are uploaded
public-read(decision 2026-07-16, explicitly reversing the same-day private-object stance — the bucket has no public read policy, so without the ACL every stored download link returnedAccessDenied). Links are unguessable (UUID keys) but public to URL holders; same convention as Admin/Core uploaders. A future truly-private requirement means presigned URLs or a proxy-download endpoint, not dropping the ACL. - Wire format is base64 JSON (
{ fileName, contentType, contentBase64 }) on template-source, attachment, and signature routes — no multipart. FiberBodyLimitis 40 MB to fit the UI’s 25 MB DOCX cap after base64 expansion.
Auth modes:
- browser / user flows:
Authorization: Bearer <JWT>+x-org - trusted machine flows:
X-Internal-API-Key
Browser CORS:
- preferred env:
CORS_ORIGIN(comma-separated Kisum frontend origins) - legacy fallback: non-wildcard
CORS_ALLOWED_ORIGINS - when
CORS_ORIGINis set,CORS_ALLOWED_ORIGINS=*is ignored - explicit allowlists enable
Access-Control-Allow-Credentialsfor credentialed browser clients
How the system is organized
Section titled “How the system is organized”The Artist module should be understood as one connected system with these capability areas:
1. Directory and identity
Section titled “1. Directory and identity”Used to maintain:
- artist records
- agencies / market companies
- contacts / people
- taxonomy
- geo
- platform and provider identities
Artist lifecycle status (2026-08-06): artists.status is active | inactive | archived |
sleeping. sleeping marks an artist too small/undiscovered to keep in the main working set —
same row, same platform IDs (artist_platform_accounts, artist_provider_refs), just parked.
Sleeping artists are excluded from GET /artists and search by default (an entity-level
BaseWhere, the same mechanism cities uses to hide non-city rows); pass ?status=sleeping to
see them. A partial index keeps the default (non-sleeping) list fast regardless of how many
artists move to sleeping. Admin exposes this as a Sleep/Wake toggle plus a dedicated Sleeping
Artists tab — see Admin API 10.2.
2. Ownership and representation
Section titled “2. Ownership and representation”Used to answer:
- who owns this company profile
- who represents this artist
- which representation is approved
- which claims are disputed
3. Availability and discovery
Section titled “3. Availability and discovery”Used to:
- publish artist availability
- search avails
- drive artist discovery and booking opportunities
- power an agency-wide availability calendar (day-by-day summary + paginated list, scoped to the caller’s own agency)
4. Booking workflow
Section titled “4. Booking workflow”Used to:
- receive inquiries
- turn requests into offers
- manage holds
- confirm bookings
- retain booking activity history
5. Contract workflow
Section titled “5. Contract workflow”Used to:
- manage company template libraries
- create booking-linked contracts
- manage versions
- manage signers and signatures
- retain contract audit history
6. Touring and logistics coordination
Section titled “6. Touring and logistics coordination”Used to:
- group bookings into tours
- manage stops and routing
- retain itinerary items
- retain booking logistics
- manage promoter-submitted hotels / hospitality / technical / transport proposals
- approve, reject, or request changes from the artist-side crew
7. Relationship intelligence
Section titled “7. Relationship intelligence”Used to:
- retain relationship memory
- keep internal notes / flags / ratings
- compute trust signals
- expose booking history
8. Read models and visibility
Section titled “8. Read models and visibility”Used to:
- power agency dashboards
- power promoter dashboards
- power shortlists
- power marketplace search
- show downstream finance visibility relevant to bookings
9. Agency provisioning (2026-05-25)
Section titled “9. Agency provisioning (2026-05-25)”Used to bridge a fresh Auth/Core company to an artists-company (the “agency”) before any operational surface is meaningful. Two flows:
- takeover — claim an existing unclaimed
artists_companiesrow - creation — propose a brand-new agency spec for admin approval
Hosted in internal/artists/provisioning/ (separate submodule from workflow/ which owns the in-app claims). Mirror of the Venues provisioning pattern with no file upload and strict one-Auth-company-per-agency enforced via DB unique partial indexes:
- one pending request per Auth org globally
- one pending takeover per (target company, requester) pair
- existing
artists_company_claims_approved_uniqkeeps enforcing “one approved org per company”
Backed by a new artists_provisioning_requests table (migration 20260525_market_provisioning_requests.sql). On approve (single tx): the service writes a real artists_company_claims row (status=‘approved’) + flips companies.claimed=TRUE + closes the request. Creation also inserts the new companies row from the stored spec first. The existing claim-based ownership checks (IsCompanyOwnedByOrg, etc.) continue to work unchanged.
Admin review goes through Backend-Kisum-Admin → /admin/artist-company-requests/* (X-Internal-API-Key proxy into this module). See the endpoint map for the 9 routes (5 tenant + 4 admin) under /api/v1/artists-companies/{sleeping,takeover-requests,creation-requests,requests} + /api/v1/me/agency + /internal/admin/artist-company-requests/*.
Frontend reading model
Section titled “Frontend reading model”Frontend teams should think about the Artist module as product sections, not as route groups.
Main frontend sections are:
- directory and profiles
- ownership and representation
- availability
- booking pipeline
- contracts
- touring
- logistics approvals
- relationship memory and trust
- dashboards and shortlists
- finance visibility
- marketplace search
The dedicated frontend guidance is in:
Main integration boundaries
Section titled “Main integration boundaries”Important remaining cross-service follow-up:
- Finance still needs explicit booking-facing internal endpoints for finance visibility
- Auth still needs final permission / role-grant alignment for the Artist module surface
- Core still needs cleaner explicit tenant-company ↔ Artist-directory company linkage
- product frontends still need to consume the backend in coherent screen flows
Recommended reading order
Section titled “Recommended reading order”Use the docs in this order:
- this page for ownership and scope
- Artist Product Vision for the conceptual product definition
- Artist Endpoint Map for what each endpoint is for and where frontend should use it
- Frontend Artist for screen structure and UI flows
- Artist Module Backend API for exact runtime route inventory
Promoter booking scope metadata
Section titled “Promoter booking scope metadata”Booking requests carry promoter event, BU, acting-user, and scope metadata. Artists stores and returns this metadata but does not decide promoter-user authorization.
Booking Buyer/Show model (2026-07-16)
Section titled “Booking Buyer/Show model (2026-07-16)”The booking pipeline was restructured around three clean parties — artist, buyer (the Kisum company paying for the show; previously scattered across promoter_company_id, talent_buyer_name and the offer’s created_by_core_company_id) and agency (the seller, previously target_company_id).
- One booking per offersheet show. Accepting an offer creates one
bookingsrow per show (offer_id+show_indexunique; migration000043). No per-show fee → the offersheet total is split equally with the cent remainder on the first show. - Bookings persist the buyer (
buyer_core_company_id) plus per-show data:show_date(plainDATE, single-day shows),show_time(HH:MM),venue_id(canonical Venue UUID),promoter_event_id,business_unit_ids,fee_amount/fee_currency, realevent_name.bookings.starts_at/ends_atno longer exist.created_by_core_company_idon a booking remains the accepting org — the buyer is a separate column. - Promoter buyer visibility (2026-07-16):
GET /bookingsand booking detail access include rows wherebuyer_core_company_idmatches the caller’sx-org, so promoter buyers see confirmed bookings after an agency accepts their exclusive offer (not only rows they created). - Paperwork on exclusive accept: contract + receivable invoice once per offer (attached to the first booking); logistics + tour stop once per booking.
- Public responses (
GET /bookings[/:id],GET /booking-offers[/:id]) return nestedartist/buyer/agency/event|shows/feeblocks; offerversions[]carrysentBy: buyer|agency. All legacy era fields (promoterCompany*,talentBuyerName,targetCompany*,startsAt/endsAt) were removed with no transition period — every consumer app was updated in the same change. - Offersheet shows accept
time(HH:MM) andeventName; migration000043backfills existing bookings from their offers’ version JSON.