Build a public apply form
Requires: A publishable key with
READ_PUBLICandWRITE_APPLICATIONSfor visitors; a secret key withMEMBER_AUTHand the member's session for signed-in members.
This guide builds a "Join" page on your site: the tiers, the form each tier asks, and the submit, for visitors and for members who are already signed in.
GET /api/v1/communities/:communityTag/segments/public
GET /api/v1/communities/:communityTag/segments/:segmentId/form
GET /api/v1/communities/:communityTag/segments/:segmentId/check-email
POST /api/v1/communities/:communityTag/segments/:segmentId/submit
POST /api/v1/communities/:communityTag/apply/checkout
POST /api/v1/communities/:communityTag/me/segments/:segmentId/apply
GET /api/v1/communities/:communityTag/me/applications
POST /api/v1/communities/:communityTag/me/applications/:requestId/checkoutReads need READ_PUBLIC, the submit needs WRITE_APPLICATIONS; both come with
the publishable key. The me/* routes need a secret key with MEMBER_AUTH and
the member's session.
1. The tiers
const tiers = await cobuntu.segments.public();
// [{ id: "seg_pro", name: "Pro", description, color, hasForm: true,
// priceMonthly: 900, priceYearly: 9000, priceOneTime: null, currency: "EUR", ... }]Prices are per cadence, in cents; null means that cadence is not offered.
A tier with every price null is free. Hidden (invite-only) tiers are not
listed.
2. The form
const form = await cobuntu.segments.form("seg_pro");
// { segmentId, title, description, formSource, fields: [{ id: "f_city", type: "TEXT", label: "City", required: true }] }Read the fields rather than hard-coding them: the form changes when a leader edits it. A hidden tier's form is served only with the invitation:
const form = await cobuntu.segments.form("seg_founders", { invitationToken });Without a valid token a hidden tier answers 404, the same as a tier that does
not exist.
3. Check the email first
const { status } = await cobuntu.segments.checkEmail("seg_pro", email);status | Do |
|---|---|
new | Continue with the form. |
has_account | They have a Cobuntu account: offer to sign in, then use me.segments.apply. |
existing_member | Already a member: send them to sign in. |
pending_application | They applied already: tell them it is under review. |
4. Submit
const r = await cobuntu.segments.submit("seg_pro", {
answers: { email, name, f_city: "Lisbon" },
billingCycle: "MONTHLY",
source: "your_site",
// invitationToken, for a hidden tier
});Then branch on the answer:
if (r.paymentRequired) {
const { checkoutUrl } = await cobuntu.apply.checkout({
requestId: r.requestId,
successUrl: returnUrl(origin, "/join/thanks"),
cancelUrl: returnUrl(origin, "/join"),
});
return redirect(checkoutUrl);
}
if (r.awaitingEmailConfirmation) return show("Check your inbox to confirm your email.");
if (r.autoApproved) return show("Welcome in. Sign in to continue.");
return show("Thanks, your application is under review.");awaitingEmailConfirmation: a publishable key cannot prove the applicant owns the address, so a submit that would be admitted at once stays pending until they click the emailed link (valid 48 hours). Submit from your server with a secret key to skip it: you vouch for the address. See Security model.paymentRequired: a paid tier. The application is held until payment.successUrlandcancelUrlmust be both or neither, on the community's site.sourcelabels where the application came from in the community's review list.- Every hidden-tier failure answers the same
404 "Segment not found".
Submits are limited to 10 per hour per IP.
Signed-in members
A member who is signed in on your site applies with their session instead, no email step:
const member = cobuntu.as(session);
const r = await member.me.segments.apply("seg_pro", { answers, billingCycle: "MONTHLY" });
switch (r.status) {
case "JOINED": return show("You are in.");
case "PENDING": return show("Your application is under review.");
case "PAYMENT_REQUIRED": {
const { checkoutUrl } = await member.me.applications.checkout(r.requestId, { successUrl, cancelUrl });
return redirect(checkoutUrl);
}
}me.applications.list() returns the member's applications, newest first, each
with its status (PENDING, PAYMENT_PENDING, RESERVED, APPROVED,
REJECTED). Offer "Complete payment" for PAYMENT_PENDING with
me.applications.checkout(requestId, { successUrl, cancelUrl }).