API
252 tRPC procedures in 39 routers and 27 REST / webhook handlers, read from the live router and the handler files on every build.
How auth works
Anyone. Built on publicProcedure.
Signed-in dancer (Bearer session token). protectedProcedure.
adminProcedure. 18 exist; they are listed only on the admin catalog.
Auth level is read from the procedure's middleware at build time, not from naming.
Routers
not a valid UUID
Magic-link claim flow for TicketTailor verified attendees.
Public festival read APIs.
Organizer insights for a festival (the "dashboard" promised on /organizers).
Current competition month key, e.g. '2026-07' (UTC).
A proposal with what the organizer needs to decide.
Public dancer profiles + self-service editing.
One review per dancer per target. Denormalizes the reviewer's name + handle onto the row so the list needs no join. Shared by review.create (a normal star review) and askLocals.recommend (an auto-5★ recommendation) — `source` distinguishes them. Manual select-then-insert/update (not onConflictDoUpdate) so it works against the hermetic FakeDb in tests too; the unique constraint is the real backstop.
Community groups directory — the cold-start filler for cities where WeDance has no events yet. Public read per city; admin/bulk create (the Commander's existing list is imported via the seed / an admin import).
"Ask locals" — a newcomer posts a question for a city; locals answer by recommending an organizer or venue. A recommendation writes into the reviews spine as an auto-5★ review (source='recommendation'), auto-stubbing the target by slug so repeated recommendations of the same place aggregate.
Professional profiles (venue / artist / organizer) resolved by handle for the unified /@<handle> route. Returns the profile + its bookable spaces (venues). Dancers are resolved separately (profile.getByUsername); the /@handle page tries this first, then the dancer.
Booking requests — connector model (WeDance holds no money). An organizer requests a date for a bookable space; the venue responds off-platform for now. Public so organizers can request without an account (email required); the caller's dancerId is captured when signed in. Terms must be accepted.
Moderator elections — community governance for free/OpenAir commons (epic G800, WED-152). A space's moderator is elected annually; candidates propose the guidelines the space runs by (G802); the community votes on an OPEN, verifiable ledger — timestamped, attributable, editable-with-history (G803); the winner's guidelines become the active ruleset the moderator enforces on the free-booking queue (G804). Eligibility: real (verified) profile, one vote per profile (G801).
Feedback router.
Gigs this dancer answers for: posted by them, or by a page they run (and the app may use).
A city slug as stored on events/hangouts, from a slug or a typed city name ("München" → "munich").
Public dated events from the `events` table — today the live mirror of wedance.vip (v3) events written by scripts/sync-v3-events.ts. Only rows that are published and not archived are listed; the 9,580 historical v4 imports stay archived/unpublished and never surface here.
Batch count of plan items per event/festival. Returns a map of itemId → count. Used by city pages to populate attendee counts on event cards without N+1 queries.
Referrals (dancer → dancer festival tickets with a discount) and, since MCP phase 2, promoter tracked links: one link per promoter page + event (server/utils/trackedLinks.ts), what each link brought, the promoter dashboard and the promo kit.
Toggle follow on a profile (artist/organizer/venue). Idempotent: if already following, unfollows; if not, follows. Returns the new state: { following: boolean }.
Business mode — artists, venues and organizers managing their own profile.
Business mode — events a profile runs, and the /business home inbox.
server_flyer_generated — every paint request (each is an AI image call). `mode`: paint (new, in a look) / direction (fresh ideas) / again / recompose (4:5 feed post) / fix / refresh. Keeping one is `server_flyer_kept`.
Artist confirmation. An organizer's photo, video or name for an artist in their line-up is a pending suggestion on the artist's profile (the event uses it right away). The organizer copies a personal link for each artist; the artist opens it, approves or changes what's there, adds more — and, when nobody owns the page yet, creates an account and owns it.
Asking venues through WeDance — the venue twin of the artist link. The organizer asks one or more venues for an event (what they need, how many guests); each venue gets a personal link (/confirm/<token>, the same page as artists) and answers: can host it, another date, or no — with its terms. The organizer compares the answers and books one; the others' links then say it was booked elsewhere. A venue whose page someone runs here also gets the ask under "Needs you" on its Business home (no notification is sent).
Booking inquiries — anyone asks to book an artist or venue from their page (/@handle, or /@handle/book on the artist's own site), no account needed. The page's team is emailed (Reply-To = the asker) and sees it under "Needs you"; nobody running it → its private booking email, else WeDance passes it on. Limits: 3 a day per asker per page, 10 a day per asker, 30 a day per page.
Business mode — an event's content plan (event_posts): the posts it's promoted with, each made with the flyer pipeline in the event's look (/business/events/<id>/flyer?post=<post>). Proposing a plan only lists the posts; nothing paints until the organizer clicks.
Business mode — a profile's flyer styles (brand_looks): the looks its flyers are painted in. "Yours" are read off its own flyers or kept from a painting; "ideas" (source 'generated') are suggestions Gemini wrote for it, painted only when picked. The description is the prompt the look is painted from, editable as text.
A profile's website and contacts, managed on WeDance (RAZ-412): /business/network/<username>/website edits the page (sections, words in every language, the welcome email), /business/network/<username>/contacts lists who signed up on it. The site reads /api/profiles/<username>/site.
Business mode — teams. Who may join a profile, with which role, and who decides:
Business mode — Network (RAZ-408). The artists, venues and organizers a page works with.
A swipe card is a photo: dancers without one never enter the deck.
Returns the active Network subscription for a profile, or null. Public so the organizers page can show the plan status without auth.
Set attendance status for an event. 'none' removes the attendance. Default visibility = hidden (opt-in, per RAZ-313).
The dancer's side of connected apps (MCP): the /oauth/authorize consent screen and /settings/connections. Web sessions only: none of these paths is in server/trpc/scopes.ts, so an app's token can never call them (an app can't approve its own consent or list other connections).
All procedures
127 queries · 125 mutations · 76 public · 176 signed-in
| Procedure | Kind | Auth | Tables |
|---|---|---|---|
| auth.requestMagicLink | mutation | public | dancers, tracked_link_actions, tracked_links |
| auth.verifyMagicLink | mutation | public | dancers, sessions |
| auth.resetPassword | mutation | public | dancers, sessions |
| auth.login | mutation | public | dancers, sessions |
| auth.register | mutation | public | booking_inquiries, dancers, sessions, tracked_link_actions, tracked_links |
| auth.changePassword | mutation | protected | dancers |
| auth.completeOnboarding | mutation | protected | dancers |
| auth.me | query | public | dancers |
| auth.myEmail | query | protected | dancers |
| auth.deleteAccount | mutation | protected | booking_requests, city_battle_votes, city_videos, community_group_reports, dancers, dinner_group_members, dinner_signups, election_candidates, election_vote_history, election_votes, festival_ride_shares, festival_roommate_lookups, festival_signups, festival_submissions, gigs, giveaway_entries, guideline_versions, hangout_rsvps, hangouts, moderator_elections, plan_items, profiles, recommendation_requests, referrals, reviews, sessions, video_votes |
| dinner.list | query | public | dancers, dinner_group_members, dinner_groups, dinner_signups, dinners, festivals |
| dinner.join | mutation | protected | dinner_signups, dinners |
| dinner.leave | mutation | protected | dinner_group_members, dinner_groups, dinner_signups |
| festivalSignup.status | query | public | festival_signups, festivals |
| festivalSignup.join | mutation | protected | events, festival_signups, festivals, tracked_link_actions, tracked_links |
| festivalSignup.createCheckoutSession | mutation | protected | festival_signups, festivals |
| festivalSignup.ticketCheckout | mutation | protected | dancers, festival_signups, festivals, referrals |
| festivalSignup.publicRoster | query | public | dancers, festival_signups, festivals |
| festivalSignup.setMyVisibility | mutation | protected | festival_signups, festivals |
| festivalSignup.getMyVisibility | query | protected | festival_signups, festivals |
| festivalSignup.myTickets | query | protected | festival_signups, festivals |
| claim.claimMyTicketHolderRows | mutation | protected | dancers, festival_signups, festivals |
| claim.getClaimStatus | query | protected | dancers, festival_signups, festivals |
| festival.bySlug | query | public | festivals |
| festival.submitDraft | mutation | public | festival_submissions |
| festivalInsights.myFestivals | query | protected | festival_submissions, festivals |
| festivalInsights.styleLevelMix | query | protected | dancers, festival_signups, festival_submissions, festivals |
| cityVideo.listApproved | query | public | city_videos |
| cityVideo.monthWinner | query | public | city_videos |
| cityVideo.getPair | query | public | city_videos, video_votes |
| cityVideo.vote | mutation | public | city_videos, video_votes |
| cityVideo.submit | mutation | public | city_videos |
| cityVideo.battleMatchup | query | public | city_battle_votes, city_videos, profiles |
| cityVideo.battleVote | mutation | public | city_battle_votes |
| cityVideo.cityLeaderboard | query | public | city_battle_votes, profiles |
| giveaway.listActive | query | public | giveaways |
| giveaway.enter | mutation | public | giveaway_entries, giveaways |
| giveaway.propose | mutation | protected | events, giveaway_proposals, profile_members, profiles |
| giveaway.proposals | query | protected | events, giveaway_proposals, profile_members, profiles |
| giveaway.decideProposal | mutation | protected | events, giveaway_proposals, giveaways, profile_members, profiles, tracked_links |
| profile.movedTo | query | public | dancers |
| profile.getByUsername | query | public | city_videos, dancers |
| profile.update | mutation | protected | dancers |
| review.list | query | public | reviews |
| review.create | mutation | protected | dancers, reviews |
| communityGroup.listByCity | query | public | community_groups |
| communityGroup.report | mutation | protected | community_group_reports, community_groups |
| askLocals.listByCity | query | public | recommendation_requests |
| askLocals.ask | mutation | protected | recommendation_requests |
| askLocals.recommend | mutation | protected | dancers, reviews |
| entity.listVenues | query | public | profiles |
| entity.listArtists | query | public | profiles |
| entity.listCities | query | public | profiles |
| entity.cityDirectory | query | public | profiles |
| entity.listByCity | query | public | profiles |
| entity.movedTo | query | public | profiles |
| entity.getByHandle | query | public | bookable_spaces, profiles |
| entity.promoterRoster | query | public | network_links, profiles |
| booking.scheduleForProfile | query | public | booking_requests |
| booking.upcomingByCity | query | public | booking_requests, profiles |
| booking.getEvent | query | public | bookable_spaces, booking_requests, profiles |
| booking.request | mutation | public | availability_slots, bookable_spaces, booking_requests, profiles |
| booking.availabilityForProfile | query | public | availability_slots, bookable_spaces |
| booking.setAvailability | mutation | protected | availability_slots, bookable_spaces, profile_members |
| election.forProfile | query | public | dancers, election_candidates, election_votes, moderator_elections |
| election.myVote | query | protected | election_vote_history, election_votes |
| election.open | mutation | protected | moderator_elections, profile_members |
| election.nominate | mutation | protected | dancers, election_candidates, moderator_elections |
| election.advanceToVoting | mutation | protected | election_candidates, moderator_elections, profile_members |
| election.castVote | mutation | protected | dancers, election_candidates, election_vote_history, election_votes, moderator_elections |
| election.close | mutation | protected | dancers, election_candidates, election_votes, guideline_versions, moderator_elections, profile_members, profiles |
| election.pendingBookings | query | protected | booking_requests, profile_members |
| election.moderateBooking | mutation | protected | booking_requests, profile_members |
| election.guidelineHistory | query | public | guideline_versions |
| feedback.inquiry | mutation | public | — |
| feedback.reporter | query | protected | dancers |
| gigs.list | query | public | gigs |
| gigs.getById | query | public | gigs |
| gigs.create | mutation | protected | gigs |
| gigs.applyTo | mutation | protected | dancers, gig_applications, gigs, profile_members, profiles |
| gigs.myApplications | query | protected | gig_applications, gigs |
| gigs.applications | query | protected | dancers, gig_applications, gigs, profile_members, profiles |
| gigs.decideApplication | mutation | protected | dancers, gig_applications, gigs, profile_members |
| gigs.close | mutation | protected | gigs |
| hangouts.listTonight | query | public | cities, events, hangout_rsvps, hangouts, plan_items |
| hangouts.listByEvent | query | public | hangout_rsvps, hangouts |
| hangouts.eventOptions | query | protected | cities, events, plan_items |
| hangouts.placeSearch | query | protected | cities, hangouts |
| hangouts.placeDetails | query | protected | hangouts |
| hangouts.create | mutation | protected | cities, dancers, events, hangout_rsvps, hangouts |
| hangouts.toggleRsvp | mutation | protected | hangout_rsvps, hangouts |
| hangouts.close | mutation | protected | hangouts |
| events.byCity | query | public | events |
| events.sameDays | query | public | cities, events |
| events.stats | query | public | cities, events |
| events.byProfile | query | public | events |
| events.get | query | public | events |
| events.festivals | query | public | dancers, events, festival_signups, festivals, plan_items |
| events.festival | query | public | events, plan_items, profile_invites, profile_members, profiles |
| plan.count | query | public | plan_items |
| plan.countBatch | query | public | plan_items |
| plan.list | query | protected | plan_items |
| plan.listDetailed | query | protected | plan_items |
| plan.add | mutation | protected | attendances, dancer_goals, events, plan_items, tracked_link_actions, tracked_links |
| plan.updateMetadata | mutation | protected | plan_items |
| plan.remove | mutation | protected | attendances, dancer_goals, events, plan_items |
| plan.goingList | query | public | dancers, festival_signups, festivals, plan_items |
| plan.myPlan | query | protected | festival_signups, festivals, plan_items |
| plan.sharedPlan | query | public | dancers, festival_signups, festivals, plan_items |
| plan.discover | query | protected | cities, dancers, discover_swipes, events, festivals, follows, plan_items |
| plan.discoverSwipe | mutation | protected | discover_swipes |
| plan.discoverUnswipe | mutation | protected | discover_swipes |
| plan.weekSocials | query | protected | cities, dancers, events, follows, plan_items |
| plan.myCourses | query | protected | events, plan_items |
| plan.goingBatch | query | public | dancers, plan_items |
| plan.setShareGoing | mutation | protected | plan_items |
| plan.getShareGoing | query | protected | plan_items |
| plan.proofShowcase | query | public | dancers, events, festivals, plan_items |
| plan.suggestions | query | public | festivals, plan_items |
| referral.validate | query | public | dancers |
| referral.myReferrals | query | public | festivals, referrals |
| referral.trackVisit | mutation | public | tracked_link_actions, tracked_links |
| referral.myLink | mutation | protected | events, profile_members, profiles, tracked_link_actions, tracked_links |
| referral.myResults | query | protected | events, profile_members, profiles, tracked_link_actions, tracked_links |
| referral.eventLinkResults | query | protected | events, profile_members, profiles, tracked_link_actions, tracked_links |
| referral.promoterDashboard | query | protected | events, giveaway_proposals, network_links, profile_members, profiles, tracked_link_actions, tracked_links |
| referral.promoKit | mutation | protected | ai_usage, brand_looks, event_flyers, event_posts, events, profile_members, profiles, tracked_links |
| rideShare.list | query | public | dancers, festival_ride_shares, festivals |
| rideShare.create | mutation | protected | events, festival_ride_shares, festivals |
| rideShare.delete | mutation | protected | festival_ride_shares, festivals |
| follow.toggle | mutation | protected | follows |
| follow.toggleDancer | mutation | protected | follows |
| follow.isFollowing | query | protected | follows |
| follow.isFollowingDancer | query | protected | follows |
| follow.areFriends | query | protected | follows |
| follow.myFollows | query | protected | follows, profiles |
| follow.count | query | public | follows |
| business.canManage | query | public | profile_members, profiles |
| business.myProfiles | query | protected | profile_claims, profile_members, profiles |
| business.search | query | protected | profiles |
| business.requestClaim | mutation | protected | profile_access_log, profile_claims, profile_members, profiles |
| business.findExisting | query | protected | profiles |
| business.create | mutation | protected | cities, dancers, profile_access_log, profile_members, profiles |
| business.get | query | protected | profile_members, profiles |
| business.spaces | query | protected | bookable_spaces, profile_members, profiles |
| business.saveSpaces | mutation | protected | bookable_spaces, booking_requests, profile_members, profiles |
| business.update | mutation | protected | cities, events, profile_members, profiles |
| businessEvents.list | query | protected | events, profile_members, profiles |
| businessEvents.canEdit | query | public | events, profile_members, profiles |
| businessEvents.get | query | protected | events, profile_members, profiles |
| businessEvents.create | mutation | protected | cities, events, profile_members, profiles |
| businessEvents.update | mutation | protected | cities, events, profile_members, profiles |
| businessEvents.duplicate | mutation | protected | events, profile_members, profiles |
| businessEvents.seriesPreview | query | protected | cities, profile_members, profiles |
| businessEvents.createSeries | mutation | protected | cities, events, profile_members, profiles |
| businessEvents.series | query | protected | events, profile_members, profiles |
| businessEvents.publishSeries | mutation | protected | events, profile_members, profiles |
| businessEvents.attendees | query | protected | attendances, dancers, events, festival_signups, festivals, plan_items, profile_members, profiles |
| businessEvents.restore | mutation | protected | events, profile_members, profiles |
| businessEvents.archive | mutation | protected | events, profile_members, profiles |
| businessEvents.lineupPhotos | query | protected | profiles |
| businessEvents.venueIdeas | query | protected | cities, events, profiles |
| businessEvents.searchProfiles | query | protected | profiles |
| businessEvents.addFromInstagram | mutation | protected | profiles |
| businessEvents.inbox | query | protected | booking_inquiries, dancers, event_flyers, event_posts, events, profile_claims, profile_invites, profile_members, profile_suggestions, profiles |
| businessFlyers.caption | query | protected | events, profile_members, profiles |
| businessFlyers.writeCaption | mutation | protected | ai_usage, events, profile_members, profiles |
| businessFlyers.saveCaption | mutation | protected | events, profile_members, profiles |
| businessFlyers.state | query | protected | brand_looks, event_flyers, event_posts, events, profile_members, profiles |
| businessFlyers.readLook | mutation | protected | ai_usage, events, profile_members, profiles |
| businessFlyers.make | mutation | protected | ai_usage, brand_looks, event_flyers, event_posts, events, profile_members, profiles |
| businessFlyers.directions | mutation | protected | ai_usage, brand_looks, event_flyers, event_posts, events, profile_members, profiles |
| businessFlyers.stop | mutation | protected | event_flyers, events, profile_members, profiles |
| businessFlyers.again | mutation | protected | ai_usage, brand_looks, event_flyers, event_posts, events, profile_members, profiles |
| businessFlyers.feed | mutation | protected | ai_usage, brand_looks, event_flyers, event_posts, events, profile_members, profiles |
| businessFlyers.fix | mutation | protected | ai_usage, brand_looks, event_flyers, event_posts, events, profile_members, profiles |
| businessFlyers.refresh | mutation | protected | ai_usage, brand_looks, event_flyers, event_posts, events, profile_members, profiles |
| businessFlyers.batch | query | protected | event_flyers, events, profile_members, profiles |
| businessFlyers.keep | mutation | protected | brand_looks, event_flyers, event_posts, events, profile_members, profiles |
| businessFlyers.copyLooks | mutation | protected | brand_looks, profile_members, profiles |
| artistInvites.suggest | mutation | protected | cities, events, profile_members, profile_suggestions, profiles |
| artistInvites.pending | query | protected | events, profile_invites, profile_members, profile_suggestions, profiles |
| artistInvites.profile | query | protected | events, profile_members, profile_suggestions, profiles |
| artistInvites.saveProfile | mutation | protected | cities, events, profile_members, profile_suggestions, profiles |
| artistInvites.link | mutation | protected | events, profile_invites, profile_members, profiles |
| artistInvites.notify | mutation | protected | dancers, events, profile_invites, profile_members, profiles |
| artistInvites.get | query | public | event_flyers, events, plan_items, profile_invites, profile_members, profile_suggestions, profiles |
| artistInvites.respond | mutation | public | cities, profile_invites, profile_members, profile_suggestions, profiles |
| artistInvites.claim | mutation | protected | profile_access_log, profile_invites, profile_members, profiles |
| venueAsks.ask | mutation | protected | events, profile_invites, profile_members, profiles |
| venueAsks.list | query | protected | events, profile_invites, profile_members, profiles |
| venueAsks.book | mutation | protected | events, profile_invites, profile_members, profiles |
| venueAsks.unbook | mutation | protected | events, profile_invites, profile_members, profiles |
| inquiries.me | query | protected | dancers, events, profile_members, profiles |
| inquiries.addToEvent | mutation | protected | events, profile_members, profiles |
| inquiries.send | mutation | public | booking_inquiries, dancers, profile_members, profiles |
| inquiries.forProfile | query | protected | booking_inquiries, profile_members, profiles |
| inquiries.setStatus | mutation | protected | booking_inquiries, profile_members, profiles |
| businessPosts.list | query | protected | event_flyers, event_posts, events, profile_invites, profile_members, profiles |
| businessPosts.writeCaption | mutation | protected | ai_usage, event_posts, events, profile_members, profiles |
| businessPosts.saveCaption | mutation | protected | event_posts, events, profile_members, profiles |
| businessPosts.writeCaptions | mutation | protected | ai_usage, event_posts, events, profile_members, profiles |
| businessPosts.propose | mutation | protected | event_posts, events, profile_members, profiles |
| businessPosts.add | mutation | protected | event_posts, events, profile_members, profiles |
| businessPosts.update | mutation | protected | event_posts, events, profile_members, profiles |
| businessPosts.remove | mutation | protected | event_posts, events, profile_members, profiles |
| businessLooks.list | query | protected | brand_looks, event_flyers, events, profile_members, profiles |
| businessLooks.create | mutation | protected | brand_looks, events, profile_members, profiles |
| businessLooks.update | mutation | protected | brand_looks, profile_members, profiles |
| businessLooks.remove | mutation | protected | brand_looks, profile_members, profiles |
| businessLooks.read | mutation | protected | ai_usage, brand_looks, events, profile_members, profiles |
| businessLooks.generate | mutation | protected | ai_usage, brand_looks, events, profile_members, profiles |
| businessSite.get | query | protected | profile_members, profile_sites, profiles |
| businessSite.save | mutation | protected | profile_members, profile_sites, profiles |
| businessSite.contacts | query | protected | profile_contacts, profile_members, profiles |
| businessSite.updateContact | mutation | protected | profile_contacts, profile_members, profiles |
| businessSite.removeContact | mutation | protected | profile_contacts, profile_members, profiles |
| businessTeam.requestAccess | mutation | protected | profile_access_log, profile_claims, profile_members, profiles |
| businessTeam.team | query | protected | dancers, profile_claims, profile_members, profiles |
| businessTeam.decideJoin | mutation | protected | dancers, profile_access_log, profile_claims, profile_members, profiles |
| businessTeam.setRole | mutation | protected | profile_access_log, profile_members, profiles |
| businessTeam.removeMember | mutation | protected | profile_access_log, profile_members, profiles |
| businessTeam.history | query | protected | dancers, profile_access_log, profile_members, profiles |
| businessNetwork.list | query | protected | network_links, profile_members, profiles |
| businessNetwork.add | mutation | protected | network_links, profile_members, profiles |
| businessNetwork.hide | mutation | protected | network_links, profile_members, profiles |
| businessNetwork.restore | mutation | protected | network_links, profile_members, profiles |
| roommate.list | query | public | dancers, festival_roommate_lookups, festivals |
| roommate.toggle | mutation | protected | events, festival_roommate_lookups, festivals |
| wave.send | mutation | protected | festival_waves |
| wave.retract | mutation | protected | festival_waves |
| wave.myWaves | query | protected | festival_waves |
| wave.wavesForMe | query | protected | dancers, festival_waves |
| wave.friends | query | protected | dancers, festival_waves |
| wave.discoverPool | query | protected | dancers, festival_waves, plan_items |
| wave.incomingCount | query | protected | festival_waves |
| organizerSubscription.getStatus | query | public | organizer_subscriptions |
| organizerSubscription.createCheckout | mutation | protected | dancers, organizer_subscriptions, profile_members, profiles |
| attendance.set | mutation | protected | attendances, events |
| attendance.mine | query | protected | attendances |
| attendance.setVisibility | mutation | protected | attendances |
| attendance.updateLogistics | mutation | protected | attendances |
| attendance.publicList | query | public | attendances, dancers |
| attendance.counts | query | public | attendances, dancers |
| attendance.proofShowcase | query | public | attendances, dancers, events |
| attendance.checkIn | mutation | protected | attendances, events, profile_members |
| oauth.authorizeInfo | query | public | dancers, oauth_clients, profile_members, profiles |
| oauth.approve | mutation | protected | oauth_clients, oauth_codes, oauth_grants, profile_members |
| oauth.deny | mutation | protected | — |
| oauth.connections | query | protected | oauth_clients, oauth_grants, profiles |
| oauth.revokeConnection | mutation | protected | oauth_grants, oauth_tokens |
| oauth.auditForProfile | query | protected | dancers, mcp_audit, oauth_clients, profile_members, profiles |
REST & webhook handlers
Nitro routes under server/api. Method and path come from the file name; purpose from the file's leading comment.
| Route | Auth | Purpose | Tables |
|---|---|---|---|
| ANY/.well-known/oauth-authorization-server | session | — (no leading comment in the file) | — |
| ANY/.well-known/oauth-protected-resource | public | — (no leading comment in the file) | — |
| ANY/.well-known/oauth-protected-resource/** | public | — (no leading comment in the file) | — |
| GET/api/architecture/admin | admin | The admin-only half of the generated API catalog (admin-level tRPC procedures, the tables they touch, admin procedures referenced by stories). Kept out of the public /docs/architecture bundle on purpose; served only to an `is_admin` dancer, using the same Bearer-session check as the tRPC adminProcedure. Generated by scripts/architecture/generate.ts into server/assets. | dancers, oauth_grants, oauth_tokens, sessions |
| GET/api/cities | public | SSR-friendly cities directory. Mirrors trpc entity.listCities but lives as a plain Nitro route so the /cities page can fetch it with useFetch during SSR (the tRPC client is client-only, which is why /cities used to flash a spinner on mount). Cities arrive already rendered in the HTML — instant load. | profiles |
| GET/api/cities/:slug | public | SSR-friendly city directory. Mirrors trpc entity.cityDirectory but as a plain Nitro route so /cities/[city] can render its people + an intent-first, data-driven <title> during SSR (the tRPC client is client-only). | profiles |
| GET/api/city-options | public | The known-city list for <CityCombobox>: fetched once per session on first focus, filtered on the client with every keystroke (no request per letter), and its biggest communities are the "Popular on WeDance" empty state. | cities, profiles |
| POST/api/city-resolve | public | Turns a picked Google suggestion into the canonical city: returns our row if we have it (no Google call), otherwise Place Details (English, + local language once per new city), upserts it into `cities` and returns it. `query` — what the person typed — is remembered as a spelling ("Мюнхен" → Munich). Before migration 0024 the city is returned with `persisted: false`. | cities, profiles |
| GET/api/city-search | public | Our canonical `cities` table first (accent/case-insensitive, alt names), then — only when that finds fewer than 3 cities and the query has 2+ letters — Google Places Autocomplete (types=(cities), English). Predictions for places we already have come back as our rows; the rest carry `source: 'google'` and must go through POST /api/city-resolve before they can be saved. `session` is the Places session token (one per typing session, client-made). | cities, profiles |
| GET/api/contacts/unsubscribe | public | The unsubscribe link in a site's welcome email (RAZ-412). The id is the secret. | profile_contacts, profiles |
| GET/api/event-status | public | published event (or booking request) behind it. The event page calls this during SSR so a missing or unpublished event answers HTTP 404 instead of an empty 200 (the page itself loads its data in the browser). | booking_requests, events |
| GET/api/festivals | public | SSR-friendly festivals directory for /festivals (useFetch during SSR, no spinner on mount). | dancers, events, festival_signups, festivals, plan_items |
| GET/api/festivals/:slug | public | Single-festival detail from the DB. Used by /festivals/[slug] as a fallback when the slug isn't in the hardcoded mock-data map, so DB-only festivals don't 404. | festivals |
| GET/api/mcp-tools | public | The MCP tool catalogue for the docs (/docs/mcp, /docs/connect-ai), straight from server/mcp/tools.ts so the docs never drift from the server. | — |
| POST/api/media/image | public | — (no leading comment in the file) | — |
| ANY/api/profiles/:username/contacts | public | Sign-ups from a profile's own website (RAZ-412): the site's form posts here (cross-origin, so CORS is open) and the person becomes one of the profile's contacts on WeDance (/business/network/<username>/contacts). The team is emailed; the person gets the welcome email the profile wrote on WeDance. Signing up again with the same email updates the contact. | dancers, profile_contacts, profile_members, profile_sites, profiles |
| GET/api/profiles/:username/events | public | Public feed of a profile's upcoming events, for the profile's own website (e.g. montuno.club reads /api/profiles/montunoclub/events). The events a profile runs (host) or organizes, published and not over, soonest first — so the website shows exactly what the profile shows on WeDance, and every row links back to WeDance for sign-up. | events, profiles |
| GET/api/profiles/:username/site | public | A profile's website in one language (RAZ-412): its sections and words as edited on WeDance (/business/network/<username>/website), plus the upcoming events the page's "events" sections list. The site renders this and nothing else, so changing the page is changing it here. | events, profile_sites, profiles |
| GET/api/qr/:file | public | The QR code of a promoter's tracked link: /api/qr/<code>.svg or .png (promo_kit, my_link). Only for codes that exist, so this is not an open QR generator; the code always points at the event on our own origin. | events, tracked_links |
| POST/api/stripe/webhook | signature | `server_subscription_cancelled`, once per organiser subscription: only while our row is not yet 'canceled' (call BEFORE the status update), so a Stripe redelivery or an `updated` + `deleted` pair counts one cancellation. A subscription we have no row for (another product on the account) is skipped. | dancers, events, festival_signups, festivals, organizer_subscriptions, referrals, tracked_link_actions, tracked_links |
| ANY/api/trpc/:trpc | session | tRPC endpoint — dispatches every procedure in the API catalog below. | — |
| POST/api/webhooks/tickettailor | signature | TicketTailor webhook receiver. | dancers, festival_signups, festivals |
| ANY/mcp | public | — (no leading comment in the file) | ai_usage, attendances, availability_slots, bookable_spaces, booking_inquiries, booking_requests, brand_looks, cities, city_battle_votes, city_videos, community_group_reports, community_groups, dancer_goals, dancers, dinner_group_members, dinner_groups, dinner_signups, dinners, discover_swipes, election_candidates, election_vote_history, election_votes, event_flyers, event_posts, events, festival_ride_shares, festival_roommate_lookups, festival_signups, festival_submissions, festival_waves, festivals, follows, gig_applications, gigs, giveaway_entries, giveaway_proposals, giveaways, guideline_versions, hangout_rsvps, hangouts, mcp_audit, mcp_rate_limits, moderator_elections, network_links, oauth_clients, oauth_codes, oauth_grants, oauth_tokens, organizer_subscriptions, plan_items, profile_access_log, profile_claims, profile_contacts, profile_invites, profile_members, profile_sites, profile_suggestions, profiles, recommendation_requests, referrals, reviews, sessions, tracked_link_actions, tracked_links, video_votes |
| ANY/mcp/public | public | — (no leading comment in the file) | ai_usage, attendances, availability_slots, bookable_spaces, booking_inquiries, booking_requests, brand_looks, cities, city_battle_votes, city_videos, community_group_reports, community_groups, dancer_goals, dancers, dinner_group_members, dinner_groups, dinner_signups, dinners, discover_swipes, election_candidates, election_vote_history, election_votes, event_flyers, event_posts, events, festival_ride_shares, festival_roommate_lookups, festival_signups, festival_submissions, festival_waves, festivals, follows, gig_applications, gigs, giveaway_entries, giveaway_proposals, giveaways, guideline_versions, hangout_rsvps, hangouts, mcp_audit, mcp_rate_limits, moderator_elections, network_links, oauth_clients, oauth_codes, oauth_grants, oauth_tokens, organizer_subscriptions, plan_items, profile_access_log, profile_claims, profile_contacts, profile_invites, profile_members, profile_sites, profile_suggestions, profiles, recommendation_requests, referrals, reviews, sessions, tracked_link_actions, tracked_links, video_votes |
| ANY/oauth/register | public | — (no leading comment in the file) | mcp_rate_limits, oauth_clients |
| ANY/oauth/revoke | session | — (no leading comment in the file) | mcp_rate_limits, oauth_clients, oauth_grants, oauth_tokens |
| ANY/oauth/token | session | — (no leading comment in the file) | mcp_rate_limits, oauth_clients, oauth_codes, oauth_grants, oauth_tokens |
REST auth is a static heuristic: signature = verifies a webhook signature, session = reads the Bearer session, admin = checks is_admin, public = none of those.