name: bc-events-create-and-edit-event description: Use when staff need to create or set up academy events (event type, tags, optional workshop asset, host, and meeting setup); do NOT use for during-event operations (bc-events-during-event), post-event wrap-up (bc-events-post-event), bulk attendee import (bc-events-bulk-import-checkins), or learner join/check-in. requires: []
Skill: Create and Edit Academy Events
When to Use
Use this skill when the user needs to create or edit an event through the academy API workflow, including selecting/creating event type, selecting tags, linking workshop assets, assigning hosts, and setting meeting provider behavior. Do NOT use this skill for bulk RSVP/attendance import (use bc-events-bulk-import-checkins), learner join/check-in, or public event browsing without editing.
Concepts
is_publiccontrols visibility/discoverability of the event.free_for_allaffects theevent_joinconsumption price path. Whentrue, join is treated as price0(no consumable spend path), but non-saas financial-status checks can still block access.free_for_bootcampsapplies whenfree_for_all=false. Eligible bootcamp users can still join through the price0path without spending anevent_joinconsumable.- When neither host nor free path applies, join requires an
event_joinconsumable compatible with the event type. tagsare validated against marketing tags of typeDISCOVERYfor the academy.asset_slugis optional workshop linkage. Only use assets withasset_typePROJECTorEXERCISE.- For online events, room automation is controlled by
create_meetand optionalmeeting_provider(dailyorlivekit). A custom provider is handled by passinglive_stream_urldirectly.
Workflow
Set request context first. Send
AuthorizationandAcademy: <academy_id>headers on all/academy/endpoints. SendAccept-Language(for exampleenores) if translated errors are needed.Resolve event type before creating or publishing an event.
- Call
GET /v1/events/academy/eventypeand pick an existingid. - If no option is suitable, create one with
POST /v1/events/academy/eventype. - If an academy event type needs refinement, update it with
PUT /v1/events/academy/eventype/<event_type_slug>.
- Call
Resolve allowed event tags.
- Call
GET /v1/marketing/academy/tag?type=DISCOVERY. - Use returned tag
slugvalues to build the eventtagsCSV string. - If you send unknown slugs or wrong tag types, event create/update fails with
tag-not-exist.
- Call
Resolve optional workshop asset.
- Call
GET /v1/registry/academy/assetwithlike=<title|slug|url>to search candidates. - Query
asset_type=PROJECTandasset_type=EXERCISEseparately (filter is exact). - Optionally validate a specific slug with
slug=<asset_slug>&asset_type=<PROJECT|EXERCISE>. - Only set
asset_slugin event payload if the selected asset type isPROJECTorEXERCISE.
- Call
Resolve optional venue for in-person events.
- Call
GET /v1/events/academy/venuesand pickvenueid whenonline_event=false.
- Call
Create the event.
- Call
POST /v1/events/academy/event. - Include required fields:
banner,capacity,starting_at,ending_at. - Include
event_type,tags, and (for online non-draft)live_stream_urlorcreate_meet=true. - For room automation, set
create_meet=trueand optionallymeeting_provider(daily/livekit). - If
meeting_provideris omitted, backend uses academy default provider, then env default (daily).
- Call
Import attendees (bulk)
After the event exists, use bc-events-bulk-import-checkins to import RSVPs and mark attendance via POST /v1/events/academy/event/<event_id>/checkin/bulk. For real-time Luma registration/check-in, use bc-events-configure-luma-webhooks instead.
Add or update host after creation.
- Assign host and send invite via
POST /v1/events/academy/event/<event_id>/host. - If profile details need adjustment, call
PUT /v1/events/academy/event/<event_id>/host/<user_id>.
- Assign host and send invite via
Edit event details.
- Single event:
PUT /v1/events/academy/event/<event_id>. - Bulk:
PUT /v1/events/academy/eventwith list payload; each item must containid. - Do not try to update
slugon PUT (try-update-slug). - If host ownership changes, re-run Step 7.
- Single event:
Hand off lifecycle operations after setup.
- While scheduled or live:
bc-events-during-event(reschedule, suspend, guest list, outside registrations, promo UTM links). - After the workshop ends:
bc-events-post-event(attendance, recording publish). - Delete only draft events with
DELETE /v1/events/academy/event/<event_id>.
- While scheduled or live:
Endpoints
| Action | Method | Path | Headers | Body | Response |
|---|---|---|---|---|---|
| List event types | GET | /v1/events/academy/eventype |
Authorization, Academy, optional Accept-Language |
Optional query filters | Non-paginated list of event types |
| Create event type | POST | /v1/events/academy/eventype |
Authorization, Academy, optional Accept-Language |
Event type payload | 201 event type object |
| Update event type | PUT | /v1/events/academy/eventype/<event_type_slug> |
Authorization, Academy, optional Accept-Language |
Event type update payload | 200 event type object |
| List selectable tags | GET | /v1/marketing/academy/tag?type=DISCOVERY |
Authorization, Academy, optional Accept-Language |
Optional pagination/query filters | Paginated tags (results) |
| Search workshop assets | GET | /v1/registry/academy/asset |
Authorization, Academy, optional Accept-Language |
Query examples below | Paginated assets (results) |
| List venues | GET | /v1/events/academy/venues |
Authorization, Academy, optional Accept-Language |
— | Non-paginated venue list |
| Create event | POST | /v1/events/academy/event |
Authorization, Academy, optional Accept-Language |
Event payload | 201 event object |
| Update one event | PUT | /v1/events/academy/event/<event_id> |
Authorization, Academy, optional Accept-Language |
Event update payload | 200 event object |
| Bulk update events | PUT | /v1/events/academy/event |
Authorization, Academy, optional Accept-Language |
List of event updates | 200 list of event objects |
| Assign host + invite | POST | /v1/events/academy/event/<event_id>/host |
Authorization, Academy, optional Accept-Language |
Host payload | 201 with user, invite, event_id |
| Update host profile | PUT | /v1/events/academy/event/<event_id>/host/<user_id> |
Authorization, Academy, optional Accept-Language |
Partial profile payload | 200 updated profile |
| Delete draft event | DELETE | /v1/events/academy/event/<event_id> |
Authorization, Academy, optional Accept-Language |
— | 204 |
List event types response example (GET /v1/events/academy/eventype)
[
{
"id": 12,
"slug": "coding-workshop",
"name": "Coding Workshop",
"technologies": "python,flask",
"description": "Live coding session",
"icon_url": "https://cdn.example.com/icons/workshop.png",
"lang": "en",
"academy": {
"id": 1,
"slug": "miami",
"name": "4Geeks Miami",
"city": {"name": "Miami"}
}
}
]
Create event type request (POST /v1/events/academy/eventype)
{
"name": "AI Workshop",
"slug": "ai-workshop",
"description": "Hands-on AI workshop",
"icon_url": "https://cdn.example.com/icons/ai-workshop.png",
"lang": "en",
"allow_shared_creation": false,
"technologies": "python,llm"
}
Update event type request (PUT /v1/events/academy/eventype/ai-workshop)
{
"name": "AI Workshop Updated",
"description": "Updated workshop description",
"icon_url": "https://cdn.example.com/icons/ai-workshop-v2.png",
"lang": "en",
"allow_shared_creation": false
}
Tag lookup response example (GET /v1/marketing/academy/tag?type=DISCOVERY)
{
"count": 2,
"next": null,
"previous": null,
"results": [
{"id": 41, "slug": "intro-webinar", "tag_type": "DISCOVERY", "subscribers": 120},
{"id": 42, "slug": "python-beginner", "tag_type": "DISCOVERY", "subscribers": 89}
]
}
Asset search request examples (GET /v1/registry/academy/asset)
/v1/registry/academy/asset?like=javascript&asset_type=PROJECT/v1/registry/academy/asset?like=https://github.com/acme/repo&asset_type=EXERCISE/v1/registry/academy/asset?slug=react-todo&asset_type=PROJECT
Asset search response example
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 55,
"slug": "react-todo",
"title": "React Todo Project",
"asset_type": "PROJECT",
"url": "https://github.com/acme/react-todo",
"readme_url": "https://github.com/acme/react-todo/blob/main/README.md"
}
]
}
Venue list response example (GET /v1/events/academy/venues)
[
{
"id": 7,
"title": "Main Campus Auditorium",
"street_address": "123 NE 1st St",
"city": "Miami",
"zip_code": "33132",
"state": "FL",
"updated_at": "2026-03-31T14:00:00Z"
}
]
Create event request (POST /v1/events/academy/event)
{
"title": "Intro to Python Live Workshop",
"banner": "https://cdn.example.com/banners/python-workshop.png",
"capacity": 80,
"starting_at": "2026-04-21T22:00:00Z",
"ending_at": "2026-04-22T00:00:00Z",
"status": "DRAFT",
"description": "Beginner-friendly workshop",
"excerpt": "Start coding with Python",
"url": "https://4geeks.com/workshops/intro-python",
"tags": "intro-webinar,python-beginner",
"lang": "en",
"event_type": 12,
"online_event": true,
"live_stream_url": "",
"create_meet": true,
"meeting_provider": "daily",
"is_public": true,
"free_for_all": true,
"free_for_bootcamps": true,
"asset_slug": "react-todo"
}
Create event response (201)
{
"id": 101,
"academy": 1,
"title": "Intro to Python Live Workshop",
"slug": "intro-to-python-live-workshop",
"banner": "https://cdn.example.com/banners/python-workshop.png",
"capacity": 80,
"starting_at": "2026-04-21T22:00:00Z",
"ending_at": "2026-04-22T00:00:00Z",
"status": "DRAFT",
"tags": "intro-webinar,python-beginner",
"lang": "en",
"event_type": 12,
"online_event": true,
"live_stream_url": "https://your-room.daily.co/event-101",
"is_public": true,
"free_for_all": true,
"free_for_bootcamps": true,
"asset_slug": "react-todo",
"eventbrite_sync_status": "PENDING",
"created_at": "2026-03-31T14:05:00Z",
"updated_at": "2026-03-31T14:05:00Z"
}
Update event request (PUT /v1/events/academy/event/101)
{
"id": 101,
"title": "Intro to Python Workshop (Updated)",
"banner": "https://cdn.example.com/banners/python-workshop-v2.png",
"capacity": 120,
"starting_at": "2026-04-21T22:00:00Z",
"ending_at": "2026-04-22T00:30:00Z",
"tags": "intro-webinar,python-beginner",
"status": "ACTIVE",
"event_type": 12,
"online_event": true,
"free_for_all": false,
"free_for_bootcamps": true
}
slug is read-only on PUT. Do not include it in update payloads.
Bulk update request (PUT /v1/events/academy/event)
[
{
"id": 101,
"title": "Python Workshop A",
"starting_at": "2026-04-21T22:00:00Z",
"ending_at": "2026-04-21T23:30:00Z"
},
{
"id": 102,
"status": "DRAFT"
}
]
Assign host request (POST /v1/events/academy/event/101/host)
{
"host": "Alex Johnson",
"host_email": "alex.johnson@example.com",
"avatar_url": "https://cdn.example.com/hosts/alex.png",
"bio": "Senior instructor and open-source contributor",
"github_username": "alexj",
"linkedin_url": "https://www.linkedin.com/in/alexj/"
}
Assign host response (201)
{
"user": {
"id": 9001,
"first_name": "Alex",
"last_name": "Johnson",
"email": "alex.johnson@example.com",
"is_active": false
},
"invite": {
"id": 300,
"status": "PENDING",
"event_slug": "intro-to-python-live-workshop",
"email": "alex.johnson@example.com"
},
"event_id": 101,
"message": "Host user created and invitation sent"
}
Update host profile request (PUT /v1/events/academy/event/101/host/9001)
{
"bio": "Lead instructor for Python track",
"portfolio_url": "https://alex.dev",
"twitter_username": "alexcodes"
}
Edge Cases
- Missing required event create fields (
banner,capacity,starting_at,ending_at) returns field-level400validation errors. - Slug collision on create returns
slug-taken. - Attempting to update
slugon PUT returnstry-update-slug. - Non-draft event without tags returns
empty-tags. - Non-draft event without
event_typereturnsno-event-type. - Event language mismatching selected event type language returns
event-type-lang-mismatch. - Online non-draft event without
live_stream_urland withoutcreate_meet=truereturnslive-stream-url-empty. - Setting
status=SUSPENDEDthrough normal event PUT returnsuse-suspend-endpoint. - Deleting non-draft event returns
non-draft-event. free_for_all=truedoes not bypass all checks: users can still be blocked by financial-status validation (cohort-user-status-later) in join flow.free_for_all=falsedoes not always mean consumable is required: host users and eligible bootcamp users may still join via price0path.- Tag slugs not available for academy
DISCOVERYtags returntag-not-exist. - Invalid tags CSV formatting (spaces, duplicate commas, leading/trailing commas) fails validation.
- Host assignment without
hostreturnshost-name-required. - Host assignment without
host_emailreturnshost-email-required. - Host update for user not assigned as event host returns
user-not-event-host. - Host update for user outside academy membership or invitation state returns
user-not-in-academyoruser-not-accepted-invitation. - Event type update for missing slug returns
event-type-not-found. - If selected workshop asset is not
PROJECTorEXERCISE, do not assign it asasset_slug.
Checklist
- Confirm headers are set:
Authorization,Academy, and optionalAccept-Language. - Resolve event type (
GET, then optionalPOST/PUT) and save validevent_typeid. - Resolve tags via
GET /v1/marketing/academy/tag?type=DISCOVERYand build validtagsCSV. - If workshop asset is needed, search and validate via
GET /v1/registry/academy/assetand only usePROJECT/EXERCISE. - If in-person event, pick
venuefromGET /v1/events/academy/venues. - Create event with
POST /v1/events/academy/eventincluding required fields and access flags (is_public,free_for_all,free_for_bootcamps). - For online meeting automation, use
create_meet=trueand optionalmeeting_provider(daily/livekit), or providelive_stream_urlfor custom provider. - Assign/update host with host endpoints after create and after any host ownership change.
- Use normal PUT for event edits; hand off to during-event or post-event skills for lifecycle operations; delete only when event is draft.