Server and app platform
Server patterns

Server patterns

Requires: A secret key holding the scope named on each route, called from your server.

Secret key only. Every route on this page acts with the community's authority: it edits tiers, sends email to the whole community, publishes content, moves money or mints member credentials. Call them from your server with a secret key holding the scope named on each route. A publishable key is refused on all of them, and none of these scopes can be added to one.

These routes are not wrapped by @cobuntu/embed, which is built for the member-facing site. Call them with fetch:

const res = await fetch(`https://api.cobuntu.com/api/v1/communities/${tag}/broadcasts`, {
  method: "POST",
  headers: { "X-API-Key": process.env.COBUNTU_SECRET_KEY!, "Content-Type": "application/json" },
  body: JSON.stringify({ subject: "October news", htmlContent: "<p>Hello members</p>" }),
});
if (!res.ok) throw new Error((await res.json()).error);

Errors are { error, code?, ...details }. Most of these older routes send no code: branch on the HTTP status. Every write here also counts against the 120 writes per minute per key limit. Full request and response schemas for each route are in the API reference.

Acting user: where a write needs one (articles, broadcasts, rejections, subscription cancels, event edits), it is recorded as made by the community's founder, because an API key is a community-owned credential rather than a person.

Membership tiers

Endpoint: PATCH /segments/{segmentId}

Scope WRITE_SEGMENTS

Edit an existing tier. Send any subset of name, description, color, isActive, priceMonthly, priceYearly, priceOneTime (all prices in cents) and currency. Answers the updated tier.

{ "priceMonthly": 1800, "isActive": true }
StatusMeaning
400 { error: "No updatable fields supplied", allowed: [...] }Nothing recognised in the body.
400A price was set before the community connected Stripe.
404No such tier in this community.

Creating and deleting tiers is not available over the API: both have billing consequences that stay in the dashboard.

Rules acceptance by email

Endpoint: POST /rules/accept

Scope WRITE_MEMBERS

Record that the person with this email accepted the community rules, for example when your own sign-up form shows the rules. You name the email, so this is server side only: the caller vouches for it.

{ "email": "alex@example.com" }

400 Valid email is required for a missing or malformed email. For a signed-in member accepting on your site, use me.onboarding.rules() instead, which identifies them by their session.

Email broadcasts

Endpoint: POST /broadcasts

Scope WRITE_BROADCASTS

Create and send an email campaign in one call.

FieldNotes
subjectRequired.
htmlContentRequired. The email body as HTML.
previewTextThe inbox preview line.
audienceall (default), approved or pending, when not sending by tier.
segmentIdsSend to these membership tiers.
includeApproved, includePendingNarrow a tier send by membership state.

Answers 201 with the sent campaign. Limits: 5 broadcasts per community per 24 hours, counting broadcasts sent from the dashboard too (429 with limit, windowHours, recentSends). 400 No recipients found (with the campaignId it created) means the audience filter matched nobody; 409 means the campaign could not be sent.

Articles

Endpoints:

Scope WRITE_ARTICLES

For a CMS or editorial pipeline that publishes into the community's journal.

  • Create takes title (required), content (HTML), excerpt, metaTitle, category, bannerUrl, keywords[] and references[] ({ label, url? }). The article is created as a draft. 201 with the article.
  • Update takes any subset of the same fields, plus status. 400 No editable fields in request body when nothing is recognised.
  • Publish requires a bannerUrl (409 Article must have a bannerUrl before it can be published). Setting status: "PUBLISHED" through PATCH skips that check, so prefer the dedicated route.
  • Publish and unpublish are idempotent and answer the article.

404 Article not found in this community for an id from elsewhere. Reading published articles is articles.list() on the client (GET /articles).

Edit an event

Endpoint: PATCH /events/{idOrSlug}

Scope WRITE_EVENTS

Edit an event by its id (not its slug). Send any subset of name, description, bannerUrl, cardImageUrl, startDate, endDate (ISO 8601, start before end), timezone (IANA, for example Europe/Lisbon), locations[] and physicalLocation.

StatusMeaning
400 No editable fields in request bodyNothing recognised.
400 startDate must be before endDate
404 Event not foundUnknown id, or another community's event.

Events that are live or have ended cannot be edited. For importers keyed on an external platform's id, use the event bridge.

Refund a sale (host side)

Endpoint: POST /sales/{saleId}/refund

Scope WRITE_SALES

The community refunds a buyer, for example after cancelling an event.

{ "reason": "Event cancelled", "refundType": "HOST_CANCELLATION", "amount": 15 }

Unit quirk: amount here is in major units (15 means 15.00), not cents. Omit it for a full refund.

refundType is HOST_CANCELLATION (default), BUYER_REQUEST or CHARGEBACK. The sale must have been sold by, or be for an event hosted by, this community (403 otherwise, 404 when unknown). Payment processing fees follow the platform refund policy. A buyer refunding their own purchase uses events.refund() or products.refund() instead.

Subscriptions

Endpoints:

Scope WRITE_SUBSCRIPTIONS

  • admin-cancel { targetUserId, reason? } cancels a member's paid membership. 404 Subscription not found when they have none.
  • change-tier { newSegmentId, billingCycle } moves a member to another tier. A free change applies at once and answers { subscription }; a paid upgrade answers { checkoutUrl } for the member to complete (send it to them, do not open it yourself). 409 when they are already on that tier.

A member changing their own tier uses me.membership.changeTier().

Elsewhere

Other server-side routes have their own pages: