Guides
Events
Save event drafts and tags

Save event drafts and tags

Requires: A secret key with MEMBER_AUTH, and the member's session.

The create-event wizard saves as the member types. These are the routes behind that, for building or extending your own create flow. All need a secret key with MEMBER_AUTH and the member's session.

GET    /api/v1/communities/:communityTag/me/drafts
GET    /api/v1/communities/:communityTag/me/drafts/event/:id
PUT    /api/v1/communities/:communityTag/me/drafts/event/:id
DELETE /api/v1/communities/:communityTag/me/drafts/event/:id
GET    /api/v1/communities/:communityTag/me/profile/events
GET    /api/v1/communities/:communityTag/me/tags
POST   /api/v1/communities/:communityTag/me/tags

Drafts

A draft is addressed by an id you mint, so the first autosave and every later one are the same PUT:

const member = cobuntu.as(session);
const draftId = crypto.randomUUID();
 
await member.me.drafts.save(draftId, {
  name: "Summer Meetup",
  draftStep: "details",
  startDate: "2026-11-20T18:00:00Z",
  communityId: null, // personal; or this community's id for a community-owned draft
});

name is required. A community-owned draft needs the member to hold EVENTS_CREATE; another community's id is 403 WRONG_COMMUNITY.

Stop autosaving on 409

When the draft is created as an event, the row is no longer a draft and every save answers 409 CONFLICT. Treat that as "done", not as an error:

try {
  await member.me.drafts.save(draftId, values);
} catch (e) {
  if (e instanceof CobuntuError && e.status === 409) stopAutosave();
  else throw e;
}

Saving on page hide

A save fired from pagehide must survive the page closing. Pass keepalive:

await member.me.drafts.save(draftId, values, { keepalive: true });

List, resume, discard

const drafts = await member.me.drafts.list();
// [{ id: "drf_1", kind: "event", name: "Summer Meetup", draftStep: "details", updatedAt, communityId: null, productType: null }]
 
const row = await member.me.drafts.get("drf_1"); // the stored values, to refill the form
await member.me.drafts.delete("drf_1");          // 204

list() returns personal drafts and this community's, never another community's. get answers 403 FORBIDDEN for another member's draft, 404 NOT_FOUND for an unknown one and 409 CONFLICT once it became an event.

Unfinished events on the profile

me.profile.events() is the member's own Events tab: every event they host, newest first, drafts included.

const events = await member.me.profile.events();
for (const e of events) {
  if (e.isDraft) {
    // "Continue setup": resume at the step they left
    link(`/events/new?draft=${e.id}`, e.draftStep);
  } else {
    badge(e.catalogStatus); // LIVE | PENDING | HIDDEN | REJECTED | DRAFT
  }
}

Read isDraft, not status: status reflects listing visibility, so a pending event reads as DRAFT there. catalogRejectionReason is set when the community declined the listing with a reason. Drafts that belong to another community are left out.

Tags

const hits = await member.me.tags.search("mus"); // at most 10; [] for an empty query
// [{ id: "tag_1", name: "Music", searchName: "music" }]
 
const tag = await member.me.tags.create("Music");

create returns the existing tag when one matches case-insensitively (200), or the new one (201). Names are up to 20 letters, digits and spaces (400 INVALID_INPUT). Send the ids as tagIds when creating the event.