Skip to content

Artist Module Backend API

Related documentation: Artist Source of Truth Registry · Artist Module Backend · Artist Product Vision · Artist Endpoint Map · Frontend Artist · Error Contract

This page is the runtime-oriented route inventory for the Artist module backend.

Use this page for:

  • exact route families that currently exist
  • auth/header rules
  • ID behavior
  • route discovery by capability area

Use Artist Endpoint Map when the question is:

  • what is this endpoint for
  • what screen should call it
  • what flow does it belong to
  • runtime: Go / Fiber
  • base path: /api/v1
  • health route: GET /health
  • docs artifact: generated OpenAPI from backend repo
  • Local dev: Fiber ReadBufferSize is 32 KiB (not the 4 KiB default) as a safety net when headers grow large.
  • Browser contract: Frontend-Kisum-Artists artistsFetch sends Authorization + x-org only (credentials: 'omit') — do not rely on cookies reaching this backend.

All /api/v1/* routes accept either:

Required headers:

  • Authorization: Bearer <JWT>
  • x-org: <core-company-id>

Required header:

  • X-Internal-API-Key: <MARKET_INTERNAL_API_KEY>

Success:

{
"success": true,
"data": {}
}

Error:

{
"success": false,
"error": {
"code": "SOME_CODE",
"message": "Readable explanation"
}
}

The service still uses mixed ID families.

Typical rule:

  • main directory entities often expose UUID path IDs
  • geo and some compatibility entities still expose integer path IDs

GET /api/v1/artists/{id} (and artist subresources that resolve the parent via artistref) accept any of:

Workflow / touring / intelligence artist routes (/availabilities, /representation, /tour-plans, booking workflow, etc.) use the same artistref resolution — not numeric-only directory ids.

FormExampleResolves via
Numeric Postgres id39229artists.id
UUID5ab409ba-3bc8-423f-811c-85d2ec00ea5cartists.uuid
Sluglady-gaga, the-weekndartists.slug (migration 000032 + batched backfill; unique index 000033)
Legacy Mongo ObjectId (24 hex)507f1f77bcf86cd799439011artists.legacy_object_id

Backfill prefers slug-shaped legacy_object_id values (e.g. ladygaga) when present, then name-based slugify. On large tables (~2M+ artists) the SQL migration only adds the column and trigger; run make backfill-artist-slugs in Backend-Kisum-Artists (default 500 rows/batch, idempotent), then make migrate-up for 000033 unique index. During backfill, slug URLs still resolve via slug-shaped legacy_object_id until artists.slug is populated. New rows get slug via DB trigger when not supplied.

Delete: DELETE /api/v1/artists/{id} (Admin list bulk delete uses this) hard-deletes the artist and cascaded directory data (profile, genres, platform links, team assignments). Blocked with 409 if booking workflow rows still reference the artist.

Companies use pkg/companyref (id / uuid / slug). People use pkg/personref (id / uuid / key).

Frontend should not guess. It should follow the generated OpenAPI and the actual returned IDs.

Main entity reads are intentionally light by default.

Use:

  • include=... for moderate detail expansion
  • dedicated subresource routes for tabs and independently loaded sections

Typical examples:

  • GET /api/v1/artists?include=profile,genres
  • GET /api/v1/companies/{id}?include=people,locations
  • POST /api/v1/marketplace/offers — create against an official or middle-agent listing; requires offerChannel: exclusive|middle_agent and rejects listing/channel mismatches.
  • Official exclusive dispatches to normal booking_offers, with the target agency resolved through approved artists_company_claims and its Core x-org. It never depends on middle_agent_operators or the Middle Agent addon.
  • Secondary middle_agent dispatches to middle_agent_offers and remains owned by the Middle Agent deal desk.
  • GET|PATCH /api/v1/marketplace/offers/{id} — neutral buyer representation over the correct underlying workflow; migrated Exclusive legacy ids resolve through aliases.
  • Legacy /api/v1/marketplace/middle-agent-enquiries[/{id}] routes are compatibility routes for Middle Agent records only; they do not fall back to official availability.
  • Exclusive actions: target agency accepts/declines, submitting promoter cancels/deletes, and either participant may counter. Every counter retains offerChannel=exclusive and the original structured offersheet.

Migration 000038_exclusive_marketplace_offer_routing moves previously misrouted Exclusive rows from middle_agent_offers into booking_offers, preserves old numeric/UUID links, and permits Core-only promoter identity on booking records.

  • GET /api/v1/artists — list supports page / limit / sort / order / q / include, plus filters countryId, iso2 (single or comma-separated ISO 3166-1 alpha-2, e.g. ID or ID,SG), active, genreId, companyId, personId

When q is present, artist names rank exact → prefix → word → contains → trigram similarity. Migrations 000045/000046 backfill and trigger-maintain artists.normalized_name; the runtime predicate uses that indexed column directly. Wrapping it in a fallback expression prevents PostgreSQL from using idx_artists_normalized_name_trgm and is a performance regression.

  • POST /api/v1/artists
  • GET /api/v1/artists/{id}
  • PATCH /api/v1/artists/{id}
  • DELETE /api/v1/artists/{id}
  • GET /api/v1/artists/{id}/profile
  • PUT /api/v1/artists/{id}/profile
  • GET /api/v1/artists/{id}/genres
  • PUT /api/v1/artists/{id}/genres
  • GET /api/v1/artists/{id}/subgenres
  • PUT /api/v1/artists/{id}/subgenres
  • GET /api/v1/artists/{id}/platform-accounts
  • PUT /api/v1/artists/{id}/platform-accounts
  • GET /api/v1/artists/{id}/provider-refs
  • PUT /api/v1/artists/{id}/provider-refs
  • GET /api/v1/artists/{id}/team
  • PUT /api/v1/artists/{id}/team
  • GET /api/v1/artists/{id}/roster
  • PUT /api/v1/artists/{id}/roster
  • GET /api/v1/companies
  • POST /api/v1/companies
  • GET /api/v1/companies/{id}
  • PATCH /api/v1/companies/{id}
  • DELETE /api/v1/companies/{id}
  • GET /api/v1/companies/{id}/people
  • PUT /api/v1/companies/{id}/people
  • GET /api/v1/companies/{id}/locations
  • PUT /api/v1/companies/{id}/locations
  • GET /api/v1/companies/{id}/genres
  • PUT /api/v1/companies/{id}/genres
  • GET /api/v1/companies/{id}/roster — derived staff-linked roster; one row per artist (deduped when multiple agents represent the same artist)
  • PUT /api/v1/companies/{id}/roster — rejected (read-only derived view)
  • GET /api/v1/companies/{id}/social-metrics
  • PUT /api/v1/companies/{id}/social-metrics
  • GET /api/v1/people
  • POST /api/v1/people
  • GET /api/v1/people/{id}
  • PATCH /api/v1/people/{id}
  • DELETE /api/v1/people/{id}
  • GET /api/v1/people/{id}/companies
  • GET /api/v1/people/{id}/platform-accounts
  • PUT /api/v1/people/{id}/platform-accounts
  • GET /api/v1/genres
  • POST /api/v1/genres
  • GET /api/v1/genres/{id}
  • PATCH /api/v1/genres/{id}
  • DELETE /api/v1/genres/{id}
  • GET /api/v1/subgenres
  • POST /api/v1/subgenres
  • GET /api/v1/subgenres/{id}
  • PATCH /api/v1/subgenres/{id}
  • DELETE /api/v1/subgenres/{id}
  • GET /api/v1/regions
  • POST /api/v1/regions
  • GET /api/v1/regions/{id}
  • PATCH /api/v1/regions/{id}
  • DELETE /api/v1/regions/{id}
  • GET /api/v1/regions/{id}/subregions
  • GET /api/v1/regions/{id}/countries
  • GET /api/v1/subregions/{id}/countries
  • GET /api/v1/countries — list supports page / limit / sort / order / q / include, plus optional bulk filters:
    • iso2s=ES,US,FR,... — comma-separated ISO 3166-1 alpha-2 codes (case-insensitive)
    • ids=1,2,3,... — comma-separated bigint country ids
    • iso2s and ids are mutually exclusive (400 if both are present)
  • POST /api/v1/countries
  • GET /api/v1/countries/{id}
  • PATCH /api/v1/countries/{id}
  • DELETE /api/v1/countries/{id}
  • GET /api/v1/countries/{id}/states
  • GET /api/v1/countries/{id}/cities
  • GET /api/v1/states — list supports page / limit / sort / order / q, plus filters countryId, parentId, stateType
  • POST /api/v1/states
  • GET /api/v1/states/{id}
  • PATCH /api/v1/states/{id}
  • DELETE /api/v1/states/{id}
  • GET /api/v1/states/{id}/cities

States are hierarchical (migration 000047). A state row carries parentId (nullable, self-reference to states.id) so a country can have as many administrative levels as it really has:

  • parentId = null — top-level division directly under the country (all pre-000047 rows, unchanged)
  • parentId = <state id> — division nested inside another division

stateType and parentId are refilled from the upstream countries-states-cities dataset (run-geo-classify.sh --state-types), which still carries the type, level and parent_id columns the original import dropped — matched on country_code + state_code (upstream iso2). parentId fills only where a country genuinely nests: France resolves to Ardennes (metropolitan department) under Grand Est (metropolitan region). Turkey stays flatstates holds only its 81 provinces, and regions such as Marmara were never imported, so nothing can nest under them until they are.

Example of the intended shape: Turkey would be region (Marmara, parentId null) → province (Istanbul, parentId = Marmara) → city (Istanbul, in cities). Indonesia has one level fewer and simply leaves parentId null on its provinces. stateType labels what each level is (region, province, state, district, …); depth is derivable — level 1 is parentId IS NULL, deeper levels follow the chain. ON DELETE SET NULL on the self-reference means deleting a parent promotes its children to top level rather than cascading. Cities carry a placeType (migration 000048). The imported reference dataset mixes real cities with administrative areas and city districts, so each row records what it actually is:

placeTypemeaningexample
nullnot classified yet — behaves exactly as before
cityreal populated placeIstanbul, Singapore
districta section of a city, not a cityBedok, Ang Mo Kio (Singapore)
localityhamlet / farm / former settlement
admin_areaa region or province filed as a city; belongs in statesMarmara (Turkey)

Companion columns geonamesId, geonamesFeatureCode and geoVerifiedAt record where the verdict came from. The backend applies this filter itselfentityConfig.BaseWhere on the cities entity, plus the shared cityPlaceTypeFilter constant for GET /countries/{id}/cities and GET /states/{id}/cities. Consumers need no change. Pass ?placeType=district to list districts or ?placeType=all to disable the filter; placeType is returned on every city row. Rows with placeType = null remain visible because most countries have not been classified yet. City lookups by id (bookings, tours, contracts, company locations) are intentionally unfiltered, so a record already pointing at a district still renders. Non-city rows are labelled, never deletedcompany_locations.city_id is ON DELETE RESTRICT and seven other tables are ON DELETE SET NULL, so deleting would either fail or silently erase locations.

Classification is done offline in bulk by scripts/run-geo-classify.sh against the GeoNames gazetteer (CC BY 4.0 — attribution required wherever the data is surfaced). It is read-only until --apply, and --apply writes only high-confidence matches. Reverse geocoding is explicitly not used here: Nominatim allows ~1 req/s and forbids bulk, and it answers “what is at this coordinate”, not “what type is this record”.

Two matching rules are load-bearing and must not be simplified back into a single lookup:

  1. A populated place always beats an administrative area. GeoNames lists many names twice — every Turkish ilçe is an A/ADM2 row and a populated place at nearly the same coordinates (Bodrum, Bergama, Nazilli, Adalar). A single nearest-match lookup labels those admin_area and drops ~182 real Turkish towns from the city list.
  2. Only feature classes A and P are considered. Otherwise a name matches a hill (T/HLL) or an industrial estate (L/INDS) — Singapore’s dump holds just 387 populated places out of 1,979 rows.

Some countries are missing their cities entirely. For Turkey the original import stored the 974 administrative districts (ilçe) where cities belong — the table holds 973 rows and contains no Istanbul, Ankara, Bursa or İzmir. Labelling cannot fix a missing row, so run-geo-classify.sh --missing reports real places above a population floor that have no row within 25 km, and --insert-missing adds them under the state whose state_code matches the GeoNames admin1. Inserted rows carry placeType = 'city' plus their geonamesId, so they stay distinguishable from the original import. Two constraints on that step are load-bearing: the state is resolved by province name, never by the provider’s admin1 code (GeoNames’ Turkish codes are its own — Ankara is 68, not plate 06), and only administrative seats (PPLC/PPLA/PPLA2/PPLA3/PPLG) are candidates, so a populous city district is never added as a city. Note cities.id has no sequence — ids are allocated from max(id).

GeoNames does not resolve everything, and the remainder is intentionally left NULL rather than guessed: Singapore planning areas are named Bedok New Town / Ang Mo Kio New Town upstream, and Istanbul ilçe are typed P/PPL rather than PPLX. Those are handled by the Wikidata pass (run-geo-classify.sh --wikidata / --wikidata-apply), which reads P31 “instance of” over the wikidata_id already stored on each city and deliberately overrides the GeoNames label — Bedok is Q104527265 planning area of Singapore, which GeoNames has no way to express. Class→placeType mapping is a database table, not code, and precedence is admin_area > district > locality > city because a planning area is also typed as a town. Items are resolved through Wikidata property P1566 (GeoNames ID) using the geonamesId this pipeline verified itself — not through the imported wikidata_id, which measured ~80% wrong (Bedok pointed at a tabletop game, Serangoon at a brown dwarf, Woodlands at the country Singapore). Rows without a verified geonamesId, or whose place has no P1566 link, get no verdict and are left unchanged.

City-states are handled by a rule, not a gazetteer. In SG, MC, VA, SM, HK, MO, GI the country has exactly one city and every other row is a district of it (run-geo-classify.sh --city-states). Neither gazetteer resolves Singapore’s planning areas — GeoNames names them Bedok New Town, and most have no Wikidata link — so the fact is encoded directly.

Wikidata P31 deliberately does not classify Turkish districts. A Turkish town and the ilçe it names are usually one Wikidata item, so Q1147395 “district of Turkey” sits on Bodrum, Fethiye, Bergama and ~185 other real towns; mapping it removes them from the city list. For Turkey the PPLA2-vs-PPLX distinction from GeoNames is authoritative instead.

  • GET /api/v1/cities
  • POST /api/v1/cities
  • GET /api/v1/cities/{id}
  • PATCH /api/v1/cities/{id}
  • DELETE /api/v1/cities/{id}
  • GET /api/v1/platforms
  • POST /api/v1/platforms
  • GET /api/v1/platforms/{id}
  • PATCH /api/v1/platforms/{id}
  • DELETE /api/v1/platforms/{id}
  • GET /api/v1/provider-sources
  • POST /api/v1/provider-sources
  • GET /api/v1/provider-sources/{id}
  • PATCH /api/v1/provider-sources/{id}
  • DELETE /api/v1/provider-sources/{id}

Ownership, representation, and availability

Section titled “Ownership, representation, and availability”
  • POST /api/v1/market-companies/{id}/claims
  • GET /api/v1/market-companies/{id}/claims
  • POST /api/v1/market-companies/{id}/claims/{claimId}/approve
  • POST /api/v1/market-companies/{id}/claims/{claimId}/reject
  • GET /api/v1/market-companies/{id}/ownership
  • POST /api/v1/artists/{id}/representation-claims
  • POST /api/v1/artists/{id}/representation-claims/{claimId}/approve
  • POST /api/v1/artists/{id}/representation-claims/{claimId}/reject
  • GET /api/v1/artists/{id}/representation
  • POST /api/v1/artists/{id}/representation/disputes
  • GET /api/v1/artists/{id}/availabilities
  • POST /api/v1/artists/{id}/availabilities
  • PATCH /api/v1/artists/{id}/availabilities/{availabilityId}
  • DELETE /api/v1/artists/{id}/availabilities/{availabilityId}
  • GET /api/v1/marketplace/artist-availabilities — unified browse (official agency avails + middle-agent listings; official boolean on each row)
  • GET /api/v1/agency/availability/summary (2026-07-14) — agency-scoped day-by-day availability rollup (own agency only, via caller’s x-org; fails closed without it). Query: from/to (YYYY-MM-DD, default today..+41d, span clamped ≤62 days), q, kind (available|hold|blocked), artistId, countryId, cityId. status='active' windows only; openavailable, soft_hold/routing_holdhold, blackoutblocked; a multi-day window counts on every UTC day it overlaps. Response includes capped (final-review fix, 2026-07-14: true when the internal 20,000-window fetch cap was hit — narrow filters for an exact count).
  • GET /api/v1/agency/availability (2026-07-14) — same filters plus page/limit/sort/order; paginated list of the agency’s active availability windows (each row carries artistName)
  • Final-review fixes (2026-07-14, same day): artistId was parsed but never applied to the SQL on either endpoint above — now scopes both. Timestamps always serialize with an explicit UTC Z suffix.
  • GET /api/v1/booking-requests
  • POST /api/v1/booking-requests
  • GET /api/v1/booking-requests/{requestId}
  • PATCH /api/v1/booking-requests/{requestId}
  • POST /api/v1/booking-requests/{requestId}/withdraw
  • DELETE /api/v1/booking-requests/{requestId} — hard-delete cancelled/withdrawn requests (promoter creator only)
  • POST /api/v1/booking-requests/{requestId}/convert-to-offer
  • GET /api/v1/booking-inbox/summary (2026-07-15) — agency nav badge counts: incomingCount = pending inbound booking_requests (target agency, not created by caller) + open/countered inbound exclusive booking_offers (commercial_terms_json.offerChannel = exclusive, same target/incoming rule). Browser-only (x-org); not for internal callers.
  • GET /api/v1/booking-offers — optional query inbox=incoming + channel=exclusive scopes the agency inbox list (offers received on the target company, excluding rows the agency created).
  • Offer list rows carry the full nested artist block (2026-07-21): artist: { id, name, imageUrl, countryId, countryName } — identical to GET /booking-offers/{offerId} and GET /bookings[/{id}]. Previously the list emitted only id + name, so every consumer rendering an offer inbox had no headshot. The list query now joins the artist image and country like the bookings list already did. Rule: the nested artist block is the same shape on every booking-workflow response — list and detail must not diverge.
  • POST /api/v1/booking-offers
  • GET /api/v1/booking-offers/{offerId}
  • POST /api/v1/booking-offers/{offerId}/counter
  • POST /api/v1/booking-offers/{offerId}/accept
  • POST /api/v1/booking-offers/{offerId}/decline
  • POST /api/v1/booking-offers/{offerId}/cancel
  • DELETE /api/v1/booking-offers/{offerId} — hard-delete terminal offers (declined, cancelled, expired) when no linked booking; promoter or agency with offer access
  • GET /api/v1/booking-offers/{offerId}/holds
  • POST /api/v1/booking-offers/{offerId}/holds
  • PATCH /api/v1/booking-offers/{offerId}/holds/{holdId}
  • GET /api/v1/bookings
  • GET /api/v1/bookings/{bookingId}
  • GET /api/v1/bookings/{bookingId}/activity
  • POST /api/v1/bookings/{bookingId}/activity
  • GET /api/v1/market-companies/{id}/contract-templates
  • POST /api/v1/market-companies/{id}/contract-templates
  • GET /api/v1/contract-templates/{templateId}
  • PATCH /api/v1/contract-templates/{templateId}
  • POST /api/v1/contract-templates/{templateId}/activate
  • POST /api/v1/contract-templates/{templateId}/archive
  • POST /api/v1/contract-templates/{templateId}/upload-source
  • GET /api/v1/contract-templates/{templateId}/variables
  • GET /api/v1/bookings/{bookingId}/contracts
  • POST /api/v1/bookings/{bookingId}/contracts
  • GET /api/v1/contracts/{contractId}
  • POST /api/v1/contracts/{contractId}/send
  • POST /api/v1/contracts/{contractId}/void
  • GET /api/v1/contracts/{contractId}/versions
  • POST /api/v1/contracts/{contractId}/versions
  • GET /api/v1/contracts/{contractId}/signatures
  • POST /api/v1/contracts/{contractId}/attachments
  • GET /api/v1/contracts/{contractId}/events
  • POST /api/v1/contracts/{contractId}/signers
  • PATCH /api/v1/contracts/{contractId}/signers/{signerId}
  • POST /api/v1/contracts/{contractId}/signers/{signerId}/send
  • POST /api/v1/contracts/{contractId}/signers/{signerId}/sign
  • POST /api/v1/contracts/{contractId}/signers/{signerId}/decline

Artist management access (touring + related workflow gates): an org may create or mutate artist-scoped touring data when it manages the artist via either path:

  1. Approved representationartist_representation_claims with claimant_core_company_id = x-org and status = approved.
  2. Staff roster / team — an active artist_team_assignments row for a person employed at the org’s approved artists-directory company (company_people + artists_company_claims).

Shared helper: pkg/orgaccess (IsArtistManagedByOrg, ManagedByOrgExistsClause). A 403 with Current organization does not manage this artist means neither path matched.

  • GET /api/v1/artists/{id}/tour-plans
  • POST /api/v1/artists/{id}/tour-plans
  • GET /api/v1/tour-plans/{tourPlanId}
  • PATCH /api/v1/tour-plans/{tourPlanId}
  • POST /api/v1/tour-plans/{tourPlanId}/stops
  • PATCH /api/v1/tour-plans/{tourPlanId}/stops/{stopId}
  • GET /api/v1/tour-plans/{tourPlanId}/routing
  • POST /api/v1/tour-plans/{tourPlanId}/routing/rebuild
  • GET /api/v1/tour-plans/{tourPlanId}/conflicts
  • GET /api/v1/bookings/{bookingId}/logistics
  • POST /api/v1/bookings/{bookingId}/logistics
  • PATCH /api/v1/bookings/{bookingId}/logistics
  • GET /api/v1/bookings/{bookingId}/itinerary
  • POST /api/v1/bookings/{bookingId}/itinerary-items
  • PATCH /api/v1/bookings/{bookingId}/itinerary-items/{itemId}
  • GET /api/v1/bookings/{bookingId}/logistics-submissions
  • POST /api/v1/bookings/{bookingId}/logistics-submissions
  • GET /api/v1/logistics-submissions/{submissionId}
  • PATCH /api/v1/logistics-submissions/{submissionId}
  • POST /api/v1/logistics-submissions/{submissionId}/submit
  • POST /api/v1/logistics-submissions/{submissionId}/approve
  • POST /api/v1/logistics-submissions/{submissionId}/reject
  • POST /api/v1/logistics-submissions/{submissionId}/request-changes
  • GET /api/v1/logistics-submissions/{submissionId}/decisions
  • GET /api/v1/relationships
  • GET /api/v1/relationships/{relationshipId}
  • GET /api/v1/relationships/{relationshipId}/notes
  • POST /api/v1/relationships/{relationshipId}/notes
  • GET /api/v1/relationships/{relationshipId}/flags
  • POST /api/v1/relationships/{relationshipId}/flags
  • GET /api/v1/relationships/{relationshipId}/ratings
  • POST /api/v1/relationships/{relationshipId}/ratings
  • GET /api/v1/relationships/{relationshipId}/trust-score
  • GET /api/v1/relationships/{relationshipId}/signals
  • GET /api/v1/artists/{id}/booking-history
  • GET /api/v1/promoters/{id}/booking-history
  • GET /api/v1/agencies/{id}/booking-history
  • GET /api/v1/bookings/{bookingId}/finance-status
  • GET /api/v1/bookings/{bookingId}/payment-schedule
  • GET /api/v1/bookings/{bookingId}/deposit-status
  • POST /api/v1/bookings/{bookingId}/finance-handoff
  • GET /api/v1/agencies/{id}/dashboard
  • GET /api/v1/agencies/{id}/roster/pipeline
  • GET /api/v1/agencies/{id}/tasks
  • GET /api/v1/agencies/{id}/activity
  • GET /api/v1/promoters/{id}/dashboard
  • GET /api/v1/promoters/{id}/shortlists
  • POST /api/v1/promoters/{id}/shortlists
  • GET /api/v1/promoters/{id}/shortlists/{shortlistId}
  • DELETE /api/v1/promoters/{id}/shortlists/{shortlistId}
  • POST /api/v1/promoters/{id}/shortlists/{shortlistId}/artists
  • DELETE /api/v1/promoters/{id}/shortlists/{shortlistId}/artists/{artistId}
  • GET /api/v1/promoters/{id}/inquiries
  • GET /api/v1/promoters/{id}/offers
  • GET /api/v1/marketplace/artists
  • GET /api/v1/marketplace/agencies
  • GET /api/v1/marketplace/promoters
  • GET /api/v1/marketplace/talent-buyers
  • GET /api/v1/marketplace/venues

Outside the auth-protected /api/v1 surface:

  • GET /health

Use:

  • this page for the exact route surface
  • Artist Endpoint Map for route purpose, frontend screen mapping, and flow explanations