Artist Module Backend API
Related documentation: Artist Source of Truth Registry · Artist Module Backend · Artist Product Vision · Artist Endpoint Map · Frontend Artist · Error Contract
Purpose
Section titled “Purpose”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 basics
Section titled “Runtime basics”- runtime: Go / Fiber
- base path:
/api/v1 - health route:
GET /health - docs artifact: generated OpenAPI from backend repo
- Local dev: Fiber
ReadBufferSizeis 32 KiB (not the 4 KiB default) as a safety net when headers grow large. - Browser contract:
Frontend-Kisum-ArtistsartistsFetchsendsAuthorization+x-orgonly (credentials: 'omit') — do not rely on cookies reaching this backend.
Auth model
Section titled “Auth model”All /api/v1/* routes accept either:
Bearer mode
Section titled “Bearer mode”Required headers:
Authorization: Bearer <JWT>x-org: <core-company-id>
Internal mode
Section titled “Internal mode”Required header:
X-Internal-API-Key: <MARKET_INTERNAL_API_KEY>
Response pattern
Section titled “Response pattern”Success:
{ "success": true, "data": {}}Error:
{ "success": false, "error": { "code": "SOME_CODE", "message": "Readable explanation" }}ID rules
Section titled “ID rules”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
Artist path :id (pkg/artistref)
Section titled “Artist path :id (pkg/artistref)”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.
| Form | Example | Resolves via |
|---|---|---|
Numeric Postgres id | 39229 | artists.id |
| UUID | 5ab409ba-3bc8-423f-811c-85d2ec00ea5c | artists.uuid |
| Slug | lady-gaga, the-weeknd | artists.slug (migration 000032 + batched backfill; unique index 000033) |
| Legacy Mongo ObjectId (24 hex) | 507f1f77bcf86cd799439011 | artists.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.
Include and subresource model
Section titled “Include and subresource model”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,genresGET /api/v1/companies/{id}?include=people,locations
Route inventory
Section titled “Route inventory”Canonical promoter marketplace offers
Section titled “Canonical promoter marketplace offers”POST /api/v1/marketplace/offers— create against an official or middle-agent listing; requiresofferChannel: exclusive|middle_agentand rejects listing/channel mismatches.- Official
exclusivedispatches to normalbooking_offers, with the target agency resolved through approvedartists_company_claimsand its Corex-org. It never depends onmiddle_agent_operatorsor the Middle Agent addon. - Secondary
middle_agentdispatches tomiddle_agent_offersand 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=exclusiveand 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.
Directory and reference routes
Section titled “Directory and reference routes”Artists
Section titled “Artists”GET /api/v1/artists— list supportspage/limit/sort/order/q/include, plus filterscountryId,iso2(single or comma-separated ISO 3166-1 alpha-2, e.g.IDorID,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/artistsGET /api/v1/artists/{id}PATCH /api/v1/artists/{id}DELETE /api/v1/artists/{id}GET /api/v1/artists/{id}/profilePUT /api/v1/artists/{id}/profileGET /api/v1/artists/{id}/genresPUT /api/v1/artists/{id}/genresGET /api/v1/artists/{id}/subgenresPUT /api/v1/artists/{id}/subgenresGET /api/v1/artists/{id}/platform-accountsPUT /api/v1/artists/{id}/platform-accountsGET /api/v1/artists/{id}/provider-refsPUT /api/v1/artists/{id}/provider-refsGET /api/v1/artists/{id}/teamPUT /api/v1/artists/{id}/teamGET /api/v1/artists/{id}/rosterPUT /api/v1/artists/{id}/roster
Companies
Section titled “Companies”GET /api/v1/companiesPOST /api/v1/companiesGET /api/v1/companies/{id}PATCH /api/v1/companies/{id}DELETE /api/v1/companies/{id}GET /api/v1/companies/{id}/peoplePUT /api/v1/companies/{id}/peopleGET /api/v1/companies/{id}/locationsPUT /api/v1/companies/{id}/locationsGET /api/v1/companies/{id}/genresPUT /api/v1/companies/{id}/genresGET /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-metricsPUT /api/v1/companies/{id}/social-metrics
People
Section titled “People”GET /api/v1/peoplePOST /api/v1/peopleGET /api/v1/people/{id}PATCH /api/v1/people/{id}DELETE /api/v1/people/{id}GET /api/v1/people/{id}/companiesGET /api/v1/people/{id}/platform-accountsPUT /api/v1/people/{id}/platform-accounts
Taxonomy, geo, platform, provider
Section titled “Taxonomy, geo, platform, provider”GET /api/v1/genresPOST /api/v1/genresGET /api/v1/genres/{id}PATCH /api/v1/genres/{id}DELETE /api/v1/genres/{id}GET /api/v1/subgenresPOST /api/v1/subgenresGET /api/v1/subgenres/{id}PATCH /api/v1/subgenres/{id}DELETE /api/v1/subgenres/{id}GET /api/v1/regionsPOST /api/v1/regionsGET /api/v1/regions/{id}PATCH /api/v1/regions/{id}DELETE /api/v1/regions/{id}GET /api/v1/regions/{id}/subregionsGET /api/v1/regions/{id}/countriesGET /api/v1/subregions/{id}/countriesGET /api/v1/countries— list supportspage/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 idsiso2sandidsare mutually exclusive (400if both are present)
POST /api/v1/countriesGET /api/v1/countries/{id}PATCH /api/v1/countries/{id}DELETE /api/v1/countries/{id}GET /api/v1/countries/{id}/statesGET /api/v1/countries/{id}/citiesGET /api/v1/states— list supportspage/limit/sort/order/q, plus filterscountryId,parentId,stateTypePOST /api/v1/statesGET /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-000047rows, 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 flat — states 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:
placeType | meaning | example |
|---|---|---|
null | not classified yet — behaves exactly as before | — |
city | real populated place | Istanbul, Singapore |
district | a section of a city, not a city | Bedok, Ang Mo Kio (Singapore) |
locality | hamlet / farm / former settlement | — |
admin_area | a region or province filed as a city; belongs in states | Marmara (Turkey) |
Companion columns geonamesId, geonamesFeatureCode and geoVerifiedAt record where the verdict came from. The backend applies this filter itself — entityConfig.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 deleted — company_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:
- A populated place always beats an administrative area. GeoNames lists many names twice — every Turkish ilçe is an
A/ADM2row and a populated place at nearly the same coordinates (Bodrum,Bergama,Nazilli,Adalar). A single nearest-match lookup labels thoseadmin_areaand drops ~182 real Turkish towns from the city list. - Only feature classes
AandPare 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/citiesPOST /api/v1/citiesGET /api/v1/cities/{id}PATCH /api/v1/cities/{id}DELETE /api/v1/cities/{id}GET /api/v1/platformsPOST /api/v1/platformsGET /api/v1/platforms/{id}PATCH /api/v1/platforms/{id}DELETE /api/v1/platforms/{id}GET /api/v1/provider-sourcesPOST /api/v1/provider-sourcesGET /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}/claimsGET /api/v1/market-companies/{id}/claimsPOST /api/v1/market-companies/{id}/claims/{claimId}/approvePOST /api/v1/market-companies/{id}/claims/{claimId}/rejectGET /api/v1/market-companies/{id}/ownershipPOST /api/v1/artists/{id}/representation-claimsPOST /api/v1/artists/{id}/representation-claims/{claimId}/approvePOST /api/v1/artists/{id}/representation-claims/{claimId}/rejectGET /api/v1/artists/{id}/representationPOST /api/v1/artists/{id}/representation/disputesGET /api/v1/artists/{id}/availabilitiesPOST /api/v1/artists/{id}/availabilitiesPATCH /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;officialboolean on each row)GET /api/v1/agency/availability/summary(2026-07-14) — agency-scoped day-by-day availability rollup (own agency only, via caller’sx-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;open→available,soft_hold/routing_hold→hold,blackout→blocked; a multi-day window counts on every UTC day it overlaps. Response includescapped(final-review fix, 2026-07-14:truewhen 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 pluspage/limit/sort/order; paginated list of the agency’s active availability windows (each row carriesartistName)- Final-review fixes (2026-07-14, same day):
artistIdwas parsed but never applied to the SQL on either endpoint above — now scopes both. Timestamps always serialize with an explicit UTCZsuffix.
Booking workflow
Section titled “Booking workflow”GET /api/v1/booking-requestsPOST /api/v1/booking-requestsGET /api/v1/booking-requests/{requestId}PATCH /api/v1/booking-requests/{requestId}POST /api/v1/booking-requests/{requestId}/withdrawDELETE /api/v1/booking-requests/{requestId}— hard-delete cancelled/withdrawn requests (promoter creator only)POST /api/v1/booking-requests/{requestId}/convert-to-offerGET /api/v1/booking-inbox/summary(2026-07-15) — agency nav badge counts:incomingCount= pending inboundbooking_requests(target agency, not created by caller) + open/countered inbound exclusivebooking_offers(commercial_terms_json.offerChannel = exclusive, same target/incoming rule). Browser-only (x-org); not for internal callers.GET /api/v1/booking-offers— optional queryinbox=incoming+channel=exclusivescopes 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 toGET /booking-offers/{offerId}andGET /bookings[/{id}]. Previously the list emitted onlyid+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 nestedartistblock is the same shape on every booking-workflow response — list and detail must not diverge. POST /api/v1/booking-offersGET /api/v1/booking-offers/{offerId}POST /api/v1/booking-offers/{offerId}/counterPOST /api/v1/booking-offers/{offerId}/acceptPOST /api/v1/booking-offers/{offerId}/declinePOST /api/v1/booking-offers/{offerId}/cancelDELETE /api/v1/booking-offers/{offerId}— hard-delete terminal offers (declined,cancelled,expired) when no linked booking; promoter or agency with offer accessGET /api/v1/booking-offers/{offerId}/holdsPOST /api/v1/booking-offers/{offerId}/holdsPATCH /api/v1/booking-offers/{offerId}/holds/{holdId}GET /api/v1/bookingsGET /api/v1/bookings/{bookingId}GET /api/v1/bookings/{bookingId}/activityPOST /api/v1/bookings/{bookingId}/activity
Contracts
Section titled “Contracts”GET /api/v1/market-companies/{id}/contract-templatesPOST /api/v1/market-companies/{id}/contract-templatesGET /api/v1/contract-templates/{templateId}PATCH /api/v1/contract-templates/{templateId}POST /api/v1/contract-templates/{templateId}/activatePOST /api/v1/contract-templates/{templateId}/archivePOST /api/v1/contract-templates/{templateId}/upload-sourceGET /api/v1/contract-templates/{templateId}/variablesGET /api/v1/bookings/{bookingId}/contractsPOST /api/v1/bookings/{bookingId}/contractsGET /api/v1/contracts/{contractId}POST /api/v1/contracts/{contractId}/sendPOST /api/v1/contracts/{contractId}/voidGET /api/v1/contracts/{contractId}/versionsPOST /api/v1/contracts/{contractId}/versionsGET /api/v1/contracts/{contractId}/signaturesPOST /api/v1/contracts/{contractId}/attachmentsGET /api/v1/contracts/{contractId}/eventsPOST /api/v1/contracts/{contractId}/signersPATCH /api/v1/contracts/{contractId}/signers/{signerId}POST /api/v1/contracts/{contractId}/signers/{signerId}/sendPOST /api/v1/contracts/{contractId}/signers/{signerId}/signPOST /api/v1/contracts/{contractId}/signers/{signerId}/decline
Touring, itinerary, and logistics
Section titled “Touring, itinerary, and logistics”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:
- Approved representation —
artist_representation_claimswithclaimant_core_company_id = x-organdstatus = approved. - Staff roster / team — an active
artist_team_assignmentsrow 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-plansPOST /api/v1/artists/{id}/tour-plansGET /api/v1/tour-plans/{tourPlanId}PATCH /api/v1/tour-plans/{tourPlanId}POST /api/v1/tour-plans/{tourPlanId}/stopsPATCH /api/v1/tour-plans/{tourPlanId}/stops/{stopId}GET /api/v1/tour-plans/{tourPlanId}/routingPOST /api/v1/tour-plans/{tourPlanId}/routing/rebuildGET /api/v1/tour-plans/{tourPlanId}/conflictsGET /api/v1/bookings/{bookingId}/logisticsPOST /api/v1/bookings/{bookingId}/logisticsPATCH /api/v1/bookings/{bookingId}/logisticsGET /api/v1/bookings/{bookingId}/itineraryPOST /api/v1/bookings/{bookingId}/itinerary-itemsPATCH /api/v1/bookings/{bookingId}/itinerary-items/{itemId}GET /api/v1/bookings/{bookingId}/logistics-submissionsPOST /api/v1/bookings/{bookingId}/logistics-submissionsGET /api/v1/logistics-submissions/{submissionId}PATCH /api/v1/logistics-submissions/{submissionId}POST /api/v1/logistics-submissions/{submissionId}/submitPOST /api/v1/logistics-submissions/{submissionId}/approvePOST /api/v1/logistics-submissions/{submissionId}/rejectPOST /api/v1/logistics-submissions/{submissionId}/request-changesGET /api/v1/logistics-submissions/{submissionId}/decisions
Relationship intelligence and read models
Section titled “Relationship intelligence and read models”GET /api/v1/relationshipsGET /api/v1/relationships/{relationshipId}GET /api/v1/relationships/{relationshipId}/notesPOST /api/v1/relationships/{relationshipId}/notesGET /api/v1/relationships/{relationshipId}/flagsPOST /api/v1/relationships/{relationshipId}/flagsGET /api/v1/relationships/{relationshipId}/ratingsPOST /api/v1/relationships/{relationshipId}/ratingsGET /api/v1/relationships/{relationshipId}/trust-scoreGET /api/v1/relationships/{relationshipId}/signalsGET /api/v1/artists/{id}/booking-historyGET /api/v1/promoters/{id}/booking-historyGET /api/v1/agencies/{id}/booking-historyGET /api/v1/bookings/{bookingId}/finance-statusGET /api/v1/bookings/{bookingId}/payment-scheduleGET /api/v1/bookings/{bookingId}/deposit-statusPOST /api/v1/bookings/{bookingId}/finance-handoffGET /api/v1/agencies/{id}/dashboardGET /api/v1/agencies/{id}/roster/pipelineGET /api/v1/agencies/{id}/tasksGET /api/v1/agencies/{id}/activityGET /api/v1/promoters/{id}/dashboardGET /api/v1/promoters/{id}/shortlistsPOST /api/v1/promoters/{id}/shortlistsGET /api/v1/promoters/{id}/shortlists/{shortlistId}DELETE /api/v1/promoters/{id}/shortlists/{shortlistId}POST /api/v1/promoters/{id}/shortlists/{shortlistId}/artistsDELETE /api/v1/promoters/{id}/shortlists/{shortlistId}/artists/{artistId}GET /api/v1/promoters/{id}/inquiriesGET /api/v1/promoters/{id}/offersGET /api/v1/marketplace/artistsGET /api/v1/marketplace/agenciesGET /api/v1/marketplace/promotersGET /api/v1/marketplace/talent-buyersGET /api/v1/marketplace/venues
Public route
Section titled “Public route”Outside the auth-protected /api/v1 surface:
GET /health
Practical documentation rule
Section titled “Practical documentation rule”Use:
- this page for the exact route surface
- Artist Endpoint Map for route purpose, frontend screen mapping, and flow explanations