My Events is an example project you install into your own account: a working sign-up app with a page for creating events, a shareable link your guests use to register, and a REST API behind both. Install it, run one setup trigger, and you have something real in front of you in a few minutes — then open the workflow and change it.
This is not a minimal starting point. It is a complete small application — 85 jobs, six API endpoints, two web pages — so you can see what a finished thing looks like on the platform before building your own. If you want the shortest path to a first response, start with Quickstart: Your first API instead.
What you get
| Part | Where it lives |
|---|---|
| Admin page — create events, see who signed up | GET /admin |
| Sign-up page — what your guests open | GET /register?id=… |
| REST API — events and registrations | /api/* |
| API reference, rendered from the spec | GET /docs |
| Storage — events and participants as Units | Two groups in your account |
Both pages are served by the workflow itself, from the same HTTP channel as the API. There is no separate web host and nothing to deploy.

A page is just an HTML body on a Send API response job — same mechanism as a JSON endpoint, different content type.
Before you start
You need one thing: an HTTP channel. Create it under Channels and note the
subdomain you choose — every URL below is relative to it, for example
https://my-events-yourname.in.bosbec.io.
Install and run it
1. Import the template. Open Workflow Builder and navigate to View > Workflow Library. Under Templates, find "My Events" and select use this template. You get one workflow containing nine triggers.
2. Connect the channel. Open each of the eight HTTP triggers and select the channel you created.
3. Name your groups. Open the create_groups trigger and find the Data
operations job at the start of it. It holds two Set data operations — the
names of the groups that will store your events and your participants. Change
them if you like, or keep the defaults.
Use plain names: letters, digits, spaces and hyphens. The lookup that follows treats the name as a regular expression, so brackets and dots can cause a miss.
4. Run create_groups once. Right-click the trigger and choose Start
trigger. It looks for each group by name, creates it if it does not exist, and
stores the two group IDs in your account settings. Every other job reads the IDs
from there, so this is the only place groups are configured.

The Data operations job holds the two group names. The right-click menu is where you run the trigger.
Running it twice is harmless — it reuses groups that already exist. But do not change the group names after you have created events, or the workflow will start looking in a new, empty group while your data sits in the old one.
5. Open /admin. Create an event. Copy the link it gives you, open it in
another tab, and sign yourself up. Go back to the admin page: your name is in
the list.
That is the whole loop. Everything from here on is about how it works and what to change.
How a request flows
Every endpoint follows the same shape: read the input, validate it in a chain of
routes, touch storage, send a response. Each validation failure has its own
Send API response job, which is why the job count is high — 24 of the 85 jobs
are responses. Jobs are named below by type and by what they do rather than by
their job_nn number, because those numbers shift as soon as you start editing.

GET /api/event end to end. The Unit pipeline finds the event by event_id,
and the JSON pipeline's item template is where a Unit's fields become the shape
the API returns.
POST /api/register is the most instructive endpoint. Its trigger checks six
things in order, each in its own route, and the first failure wins:
| Check | Failure |
|---|---|
| Event ID in the query string | 400 missing_event_id |
| Event exists | 404 wrong_event_id |
| Email present and valid | 400 missing_or_invalid_email |
| Not already registered | 409 already_registered |
| Name present | 400 missing_name |
| Spots left | 409 event_full |
Read the routes from the trigger downwards and they come in that order. Only
after the last one does the Unit pipeline write the participant Unit, a Data
operations job increment spots_taken, and the final Send API response
return 200 success.
The order matters more than it looks. Validate the body before checking capacity, or a guest who forgot to type their name is told the event is full.
Two Unit types
Events and participants are both stored as Units, in the two groups you created.
An event Unit carries event_id, event_name, event_description,
event_start_datetime, event_nbr_of_spots and spots_taken. It also sets
firstname to the event ID and lastname to the event name, which is what
gives the Unit a readable label in lists.
A participant Unit carries email, name and the event_id of the event they
signed up for. That event_id is the only link between the two — there is no
relation type, just a matching value, and every lookup filters on it.
Reading the API
The full contract is in openapi.yaml, served at /api/openapi.yaml and
rendered for reading at /docs. Two conventions worth knowing before you read
it:
Successful reads return data directly. Everything else returns
{ "status": "...", "message": "..." }. Branch on status — it is stable.
message is for humans and may be reworded.
Numbers are numbers. nbr_of_spots and spots_taken are integers, not strings.
The same contract in one file, with request bodies, example responses and every
status value, is available here:
Extend it
The template deliberately stops short of a production system. Here are the natural next steps, roughly in order of difficulty.
Add a field to events
Say you want a location. It touches five places, and three of them are in the
POST /api/create trigger, in the order the jobs run:
- The request — accept
locationin thePOST /api/createbody. - The Parse JSON to resource job — where the incoming JSON is read.
- The Unit pipeline — add
metadata.event_locationto the field mapping, following thedefaultvalue(...)pattern the other fields use so a missing value becomes an empty string rather than an error. - The JSON pipeline — include it in the response.
- The pages — the form in
/adminand the display in/register, each the HTML body of that trigger's Send API response.
Adding a field to participants — dietary requirements, for instance — is the same
shape in the POST /api/register trigger: the Parse JSON to resource, the
Unit pipeline that writes the participant, and the sign-up form.
Add a registration deadline
Store a registration_closes timestamp on the event, then add a route in the
POST /api/register chain that compares it to the current time and returns
409 registration_closed. Put it next to the capacity check — both are
questions about whether the event can still accept anyone.
Other ideas
- Waiting list — when
event_full, store the participant with awaitingflag instead of refusing them. - Confirmation email — send a message when registration succeeds. You will need the channel's absolute URL for the link, since there is no browser to supply it. Store it in account settings, the way group IDs are stored.
- Editing events — there is no
PUT. Add one. - Authentication — the admin page is open to anyone who knows the URL.
When something does not work
Most of the confusion in a workflow like this comes from three platform behaviours that fail silently: the run succeeds, no error appears, and a wrong value carries on. An expression that cannot resolve renders as its own text, a Set data operation with an empty result leaves the old value in place, and a route with Compare value is regex enabled ignores its compare operator.
None of them are specific to this project — you will meet all three in anything you build. When a job succeeded but did the wrong thing has them together, with the detail in Working with Variables and Route From Meta Data.