Skip to content

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.

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.

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

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=1 refreshes from booking inbox summary + agency tasks; workflow mutations trigger SyncPending
  • booking-level finance visibility and finance handoff audit
  • users
  • sessions
  • JWT
  • memberships
  • permission truth
  • tenant companies
  • business units
  • subscriptions
  • packages
  • add-ons
  • entitlement truth
  • 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: Go + Fiber
  • route base: /api/v1
  • source of truth: Market Postgres (artists_data in production; artists_demo when APP_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

Backend-Kisum-Artists keeps two Postgres URLs in .env. Only make run / go run ./cmd (the API server) picks from APP_ENV:

APP_ENVDSN env varPurpose
developmentDEMO_DATABASE_URLDemo 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.

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/source serves 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 into variable_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) adds artist_availabilities.contract_terms_json, companies.legal_profile_json, booking_offers.mutual_terms_acceptance_json (all nullable JSONB).
  • GET/PATCH /api/v1/companies/:id reads/writes legalProfileJson (address, representative name, 6 bank fields) — fills AGENCY_COMPANY_ADDRESS, AGENCY_REP_NAME (fallback: generating user), and the AGENCY_BANK_* markers.
  • Availability create/update accept contractTerms (contracting company name/address, production responsibility, tech-production, comps, travel party) — fills ARTIST_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 } into booking_offers.mutual_terms_acceptance_json. Offer detail now exposes top-level mutualTerms (proposed) and mutualTermsAcceptance (persisted snapshot).
  • Accept gating: accepting an EXCLUSIVE offer whose offersheet carries mutualTerms requires 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 → 422 MUTUAL_TERMS_NOT_ACCEPTED (message starts “Mutual terms”). An unrecognized-but-present mutualTerms shape fails closed rather than skipping the gate. Offers with no mutualTerms skip 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_json best-effort snapshots into the accepted version’s offersheet.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 mutualTerms is 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/BILLING now read from the booking’s own show row; SPONSORS renders YES/NO; MERCHANDISING renders “Artist X% / Promoter Y%” (+ notes); TICKET_SCALLING renders one line per ticket-scaling row.
  • Mutual-terms acceptance keys: billing is accepted per showbilling:<showIndex> replaces the single mutualTerms.billing key from Phase C above (one accept row per show in MutualTermsDialog, 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_DATE unresolved.
  • Planned Phase D (signer-based COMPANY_SIGNATORY_NAME fill) is OBSOLETE and will not be built — the product owner’s v3 template (72 markers) no longer contains that marker; the signature block uses BUYER_SIGNATORY_* + SIGNATURE_DATE only.

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):

ContentKey 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_PREFIX stays 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 returned AccessDenied). 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. Fiber BodyLimit is 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_ORIGIN is set, CORS_ALLOWED_ORIGINS=* is ignored
  • explicit allowlists enable Access-Control-Allow-Credentials for credentialed browser clients

The Artist module should be understood as one connected system with these capability areas:

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.

Used to answer:

  • who owns this company profile
  • who represents this artist
  • which representation is approved
  • which claims are disputed

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)

Used to:

  • receive inquiries
  • turn requests into offers
  • manage holds
  • confirm bookings
  • retain booking activity history

Used to:

  • manage company template libraries
  • create booking-linked contracts
  • manage versions
  • manage signers and signatures
  • retain contract audit history

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

Used to:

  • retain relationship memory
  • keep internal notes / flags / ratings
  • compute trust signals
  • expose booking history

Used to:

  • power agency dashboards
  • power promoter dashboards
  • power shortlists
  • power marketplace search
  • show downstream finance visibility relevant to bookings

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_companies row
  • 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_uniq keeps 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 teams should think about the Artist module as product sections, not as route groups.

Main frontend sections are:

  1. directory and profiles
  2. ownership and representation
  3. availability
  4. booking pipeline
  5. contracts
  6. touring
  7. logistics approvals
  8. relationship memory and trust
  9. dashboards and shortlists
  10. finance visibility
  11. marketplace search

The dedicated frontend guidance is in:

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

Use the docs in this order:

  1. this page for ownership and scope
  2. Artist Product Vision for the conceptual product definition
  3. Artist Endpoint Map for what each endpoint is for and where frontend should use it
  4. Frontend Artist for screen structure and UI flows
  5. Artist Module Backend API for exact runtime route inventory

Booking requests carry promoter event, BU, acting-user, and scope metadata. Artists stores and returns this metadata but does not decide promoter-user authorization.

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 bookings row per show (offer_id + show_index unique; migration 000043). 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 (plain DATE, single-day shows), show_time (HH:MM), venue_id (canonical Venue UUID), promoter_event_id, business_unit_ids, fee_amount/fee_currency, real event_name. bookings.starts_at/ends_at no longer exist. created_by_core_company_id on a booking remains the accepting org — the buyer is a separate column.
  • Promoter buyer visibility (2026-07-16): GET /bookings and booking detail access include rows where buyer_core_company_id matches the caller’s x-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 nested artist / buyer / agency / event|shows / fee blocks; offer versions[] carry sentBy: 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) and eventName; migration 000043 backfills existing bookings from their offers’ version JSON.