Onboarding operations
Club-scoped onboarding query, special blocks, attachments, and validation.
onboardings(clubId: ID!) returns every onboarding owned by a club. Use
onboarding(clubId: ID!, id: ID!) for one record. Each returns id, clubId,
name, description, revision, and ordered questions
plus afterApproval and hosted configurations. Hosted settings include enabled, slug,
font, and primaryColor. Each block includes id, title,
type, required, options, nullable documentName, nullable documentData, nullable
personField and attributeKey targets, the answer format, and, for identity verification and
booking blocks, identityVerification and reservationCategoryId. Questions and after-approval
steps share this shape.
query ClubOnboardings($clubId: ID! = "51000000-0000-4000-8000-000000000051") {
onboardings(clubId: $clubId) {
id
clubId
name
description
revision
questions {
id
title
type
required
options
documentName
documentData
}
afterApproval {
redirectUrl
steps {
id
type
documentName
}
}
}
}The example defaults to the seeded Ukab Clubs club and runs with empty variables ({}).
Override clubId with an ID from clubDirectory to inspect another club. An unavailable
club returns an empty list.
hostedOnboarding(slug: String!, id: ID!) is the public lookup used by
https://<club-slug>.champagne.dev-ukab.fr/onboarding/<onboarding-id>. The slug belongs to the club and is
configured once in club settings; id selects one of that club's published onboardings. Disabled,
unknown, or mismatched combinations return null.
updateClubHostedSlug(clubId: ID!, slug: String) sets that club-wide slug and returns the updated
Club (id, name, hostedSlug, ...). It requires settings_manage in the club. The slug is
trimmed and lowercased before validation and must be 3–63 lowercase letters, numbers, or inner
hyphens. The platform's own subdomains (app, api, consumer, docs, mail, news, staging,
www) are refused with BAD_USER_INPUT. null or a blank string removes it, which takes every
hosted onboarding of the club offline. Slugs are unique across clubs: a slug already used by
another club returns BAD_USER_INPUT ("This address is already used by another club.") and
leaves both clubs unchanged. An unknown club returns BAD_USER_INPUT ("This club is
unavailable."). The change is recorded in the club's activity history.
mutation SetHostedSlug($clubId: ID!, $slug: String) {
updateClubHostedSlug(clubId: $clubId, slug: $slug) {
id
hostedSlug
}
}createOnboarding(input: CreateOnboardingInput!) accepts a clubId, name, and
description. It creates a revision-one onboarding with required name and email-verification
blocks and returns { onboarding, error }. The generated onboarding ID is a compact value such as
onb_4f3a2b1c9d; seeded journeys use readable IDs such as common-members.
saveOnboarding(input: SaveOnboardingInput!) accepts id, clubId, name,
description, the loaded revision, the complete ordered questions array, and
afterApproval, and hosted. Hosted settings retain per-onboarding enablement, font, and primary
color; the shared subdomain is not part of this input. It edits that onboarding atomically, increments its revision, and returns
{ onboarding, error }. A stale revision or unavailable club/onboarding returns
a readable error. For a club with the billing module turned off, a PLAN_SELECTION or
PLAN_PAYMENT step returns the error "Payments are turned off for this club.", and
onboarding, onboardings and hostedOnboarding leave those steps out; see
Clubs with billing turned off.
mutation SaveOnboarding($input: SaveOnboardingInput!) {
saveOnboarding(input: $input) {
onboarding {
id
revision
}
error
}
}An OnboardingQuestionInput supplies id, title, type, required, and
options. For ordinary choice questions, options contains labels. For plan selection,
it contains the plan IDs offered during onboarding; an empty list retains the legacy behavior of
offering every currently available club plan. Other special blocks use required: true and
options: [].
| Type | Rules |
|---|---|
SHORT_ANSWER | Only in questions; empty options; optional personField or attributeKey target |
LONG_ANSWER | Only in questions; empty options; optional text attributeKey target |
OPTIONS | Only in questions; 2–20 distinct choices (case-insensitive after trimming), or choices taken from its personField or attributeKey target |
PLAN_SELECTION | Only in questions; at most one; up to 100 distinct plan IDs in options |
PLAN_PAYMENT | Only in afterApproval.steps; at most one; requires plan selection |
DOCUMENT_SIGNATURE | Only in afterApproval.steps; at most one; required PDF attachment |
IDENTITY_VERIFICATION | Either side; at most one overall; always required; optional identityVerification checks; refused when Stripe Identity is not configured |
EMAIL_OTP | Only in questions; exactly one, required; empty options |
PHONE_OTP | Either side; at most one overall; always required; empty options; verifies a mobile number by text message and saves it as the person phone number |
PERSON_NAME | Only in questions; exactly one, required; empty options |
PERSON_ADDRESS | Either side; at most one overall; empty options; collects a structured Address (line1, line2, city, postalCode, state, country) saved as the person address |
RESERVATION_BOOKING | Either side; at most one overall; requires reservationCategoryId (see Booking step); required may be false |
"Either side" blocks can be placed in questions (answered while the application is a draft) or
in afterApproval.steps (answered once accepted), but only once across both lists. Before approval,
IDENTITY_VERIFICATION and RESERVATION_BOOKING must come after the PERSON_NAME and
EMAIL_OTP blocks. Address and phone answered before approval are forwarded to the person on
acceptance; after approval they are saved on the person immediately. Breaking these rules returns
BAD_USER_INPUT with one of:
- "After approval can only contain plan payment, document signature, address, phone verification, identity verification and booking steps."
- "Plan payment and document signature belong in After approval."
- "Applicant address can appear only once."
- "Phone verification can appear only once and is always required."
- "Identity verification can appear only once."
- "Booking can appear only once."
- "Place identity verification and booking after the applicant name and email."
The afterApproval object contains an ordered steps array and an optional absolute HTTP(S)
redirectUrl. A custom frontend receives the application ID in that URL's applicationId query
parameter. When the URL is omitted, approval emails link to Champagne's consumer app.
Names allow 1–200 trimmed characters and descriptions 1–1000. Onboardings contain
1–50 application blocks with unique UUIDs. Onboarding IDs contain 3–32 lowercase letters,
numbers, underscores, or hyphens and begin with a letter. Block titles allow 1–500
trimmed characters and option labels 1–200. Invalid inputs yield
GraphQL BAD_USER_INPUT before any database write.
Hosted slugs use 3–63 lowercase letters, numbers, and hyphens, cannot begin or end with a hyphen,
and cannot use reserved Champagne service labels. Enabled slugs are globally unique. The font is
one of MODERN, CLASSIC, or EDITORIAL; the primary color is a six-digit hexadecimal color.
Supply both documentName and canonical base64 documentData for signature
blocks. Names allow up to 200 characters and must end in .pdf. The API checks
the PDF header and a 2 MiB decoded size per document; total document data is
limited to 4 MiB. Other block types reject attachments.
Booking step
A RESERVATION_BOOKING block lets an applicant book a visit, for example a discovery tour, either
while applying or once accepted, before becoming a member. reservationCategoryId names the
reservation category it books; other block types reject the field with BAD_USER_INPUT, and a
booking block without a valid UUID receives
BAD_USER_INPUT: "Choose the reservation category of each booking step.". These rules apply on
either side. When saving, the category must also be:
- a category of the same club; a category of another club or one that no longer exists returns
error: "Choose a reservation category of this club for each booking step."; - booked by time slot (
slotBooking), otherwiseerror: "Choose a reservation category booked by time slot."; - free: no resource with an
amount, adepositAmountor a plan price above 0, otherwiseerror: "Onboarding visits are free. Choose a category without prices.". Onboarding never takes payment for a booking; charge for later visits through billing.
Mark the category onboardingOnly to keep it out
of member booking. A category used by a booking block on either side cannot be deleted while the
block remains: deleteReservationCategory returns
error: "An onboarding booking step uses this category. Remove the step first.". Applicants book
through the application booking operations.
Question targets and answer formats are described in Member attributes.
Application execution is handled by application operations. Payment and document signatures are available only after acceptance. Payment uses hosted Stripe Checkout (see application operations); document signatures use Yousign's hosted flow and a server-verified completion operation. Email verification sends a real message through the configured mail provider.
IDENTITY_VERIFICATION asks applicants to verify an ID document with
Stripe Identity. Its
identityVerification: IdentityVerificationSettingsInput sets the checks; it is only accepted on
this block type and defaults to:
| Field | Default | Rule |
|---|---|---|
minimumAge | 18 | Integer 0–99 compared with the document's date of birth; 0 disables the age check |
requireSelfie | true | Stripe compares a selfie with the document photo (require_matching_selfie) |
requireNameMatch | true | Every word of the applicant's first and last name must appear in the document's name |
identityVerificationAvailable(clubId: ID!): Boolean! tells staff editors whether this server can
run Stripe Identity. When it is false, saveOnboarding refuses an onboarding that contains the
step with BAD_USER_INPUT "Identity verification with Stripe Identity is not configured on this
server." The query needs staff access to the club.
query IdentityVerificationAvailable($clubId: ID!) {
identityVerificationAvailable(clubId: $clubId)
}SPONSORS adds a required sponsorship question answered with one-time sponsor codes
(see sponsorship). sponsorCount accepts 1–5 (default 2)
and is returned on questions; it is null for other block types. One sponsorship block is
allowed per onboarding. The configuration is copied into new applications.