Show community content
Requires: A publishable key with
READ_PUBLICfor reads; a secret key withMEMBER_AUTH(and the member's session) for onboarding,WRITE_SALESfor donations.
The reads a site makes around its events and shop: who the community is, who
is in it, what it teaches and writes, and the onboarding a new member still
owes. Everything here is READ_PUBLIC unless noted, so the publishable key
works for the reads, and a server with the secret key works for all of it.
GET /api/v1/communities/:communityTag/branding
GET /api/v1/communities/:communityTag/members
GET /api/v1/communities/:communityTag/members/:userId
GET /api/v1/communities/:communityTag/members/:userId/membership
GET /api/v1/communities/:communityTag/courses
GET /api/v1/communities/:communityTag/articles
GET /api/v1/communities/:communityTag/rules
GET /api/v1/communities/:communityTag/places/cities
GET /api/v1/communities/:communityTag/me/onboarding
POST /api/v1/communities/:communityTag/me/onboarding/data
POST /api/v1/communities/:communityTag/me/onboarding/rules
POST /api/v1/communities/:communityTag/products/:sku/donate
GET /api/v1/communities/:communityTag/products/:sku/donations/summaryLook like the community
const branding = await cobuntu.branding();
// { name: "Acme", iconUrl: "https://...", theme: { ... }, communityUrl: "https://your-community.cobuntu.com" }theme is the community's own theme document, passed through as stored. Read
the tokens you know (colours, fonts) and ignore the rest; it is {} when the
community has none. Branding is public by nature, so cache it:
const branding = await cobuntu.branding({ next: { revalidate: 3600, tags: ["branding"] } });Show the members
const { members, pagination } = await cobuntu.members.list({ limit: 24, offset: 0 });
const alex = await cobuntu.members.get("alex"); // user id or usertagEach Member carries name, usertag, profileImage, bio, pronouns,
roleGroups, joinedAt, friendsCount and a profileUrl on the community
app. These reads follow the community's Members page setting exactly as an
anonymous visitor sees it:
| Members page | What you get |
|---|---|
| Public | The roster, without members who chose to hide from the directory. A hidden member's get is 404. |
| Members only | 403 MEMBERS_ONLY. For a signed-in member, use cobuntu.as(session).me.directory(). |
| Switched off | 404 PAGE_DISABLED. Hide the section. |
try {
return await cobuntu.members.list();
} catch (e) {
if (e instanceof CobuntuError && (e.code === "MEMBERS_ONLY" || e.code === "PAGE_DISABLED")) return null;
throw e;
}Before granting anything privileged to a person, check their standing with
members.membership(userId) rather than trusting what your own session says.
Courses
const { courses, pagination } = await cobuntu.courses.list({ category, limit: 12 });
const course = await cobuntu.products.get(courses[0].id); // the detail is a productCourses use their own taxonomy (categories({ type: "COURSE" })) and only
public ones are listed. The list is 404 NOT_FOUND when the community does
not run courses, so treat that as "no courses section".
Articles and rules
const { articles, pagination } = await cobuntu.articles.list({ limit: 10 });
const article = await cobuntu.articles.get("welcome-to-acme"); // or null
const rules = await cobuntu.rules(); // CommunityRule[], in display orderarticles.get pages through the published list, so cache it. Each rule has a
title, an optional description, and an optional linkUrl / linkLabel to
a document the member must be able to open before accepting.
Onboarding a signed-in member
After sign-up the community may still need the rules accepted and a member data form filled. The read returns everything needed to draw the pending step:
const member = cobuntu.as(session);
const ob = await member.me.onboarding.get();
if (!ob.complete && ob.nextStep === "rules") {
// render ob.rules, then:
await member.me.onboarding.rules();
}
if (!ob.complete && ob.nextStep === "data" && ob.form) {
await member.me.onboarding.data({ segmentId: ob.form.segmentId, answers: { [fieldId]: "Lisbon" } });
}These need the secret key with MEMBER_AUTH and the member's session. Answers
are validated against the form: an unknown field, a missing required field or
a value outside a field's options is 400 INVALID_INPUT, and a tier whose form
lives elsewhere is 400 FORM_NOT_NATIVE. Render every rule (and make each
linked document reachable) before you call rules(): it records what the
member was shown.
City autocomplete
const cities = await cobuntu.places.cities({ q: input, lang: "en" });
// [{ placeId, description: "Lisbon, Portugal", mainText: "Lisbon", secondaryText: "Portugal", countryCode: "PT" }]Use it for the location field on a profile or sign-up form. It never fails a
form: problems come back as [], a query under 2 characters answers [], and
you get at most 6 suggestions. It is limited to 60 lookups a minute per
visitor IP, so debounce keystrokes (about 250 ms) and pass endUserIp when you
proxy it through your server. Save description (or mainText) into the
profile's location with me.profile.update.
Support a maker
A donation to a product, without buying it:
const { checkoutUrl } = await cobuntu.products.donate("acme-notebook", {
donation: { amount: 1000 }, // cents
customer: { email }, // required without a member session
successUrl: returnUrl(origin, "/thanks"),
cancelUrl: returnUrl(origin, "/marketplace/acme-notebook"),
});
// redirect to checkoutUrl
const { total, count, currency } = await cobuntu.products.donationsSummary("acme-notebook");Donating needs WRITE_SALES, which the default publishable key does not
carry: call it from your server. Branch on INVALID_DONATION_AMOUNT; a
missing email or return URL is a 400 without a code. The summary carries no
donor identity. Event donations work the same way through events.donate (see
Events on your site).