Documentation

Running an Event

Create an activity and sessions, gate entry, check people in at the door, and reward attendance automatically.

This guide runs an event from nothing to a rewarded attendee: create the activity and a session, decide how entry is gated, admit people at the door, and have Davi issue a reward on the fifth check-in. Everything here needs an organization-scoped token.

1. Create the activity and a session

An activity is the event. A session is one occurrence of it, and people check in to sessions.

curl -X POST "https://api.davi.social/api/v1/organizations/ORG_SLUG/activities" \
  -H "Authorization: Bearer ORG_SCOPED_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Friday Meetup" }'

curl -X POST "https://api.davi.social/api/v1/activities/ACTIVITY_SLUG/sessions" \
  -H "Authorization: Bearer ORG_SCOPED_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Week 1" }'

Model a recurring event as one activity with many sessions. That is what lets a trigger count "attended five times".

2. Decide how entry is gated

There are three gates. Each is checked separately, and passing one does not satisfy another.

GateSet byEffect
Ticketrequires_ticket on the activity or sessionThe attendee must already hold a valid ticket
Entitlementrequired_entitlement on the activity or sessionThe attendee's membership tier must grant that key
Prior sessionAn earlier session the attendee must have attendedCheck-in is refused, naming the session that is missing
  • A session inherits from its activity and can override it, so a members-only activity can still have one open session.
  • The prior-session gate is answered by the person, not the card: someone who attended on one card and turns up with another has still attended.
  • Registering means holding a ticket, and a ticket is a reward. There is no separate register call. The activity names a reward template in ticket_template_uuid, and you issue tickets by redeeming that template to the attendee.
  • An anonymous walk-in cannot pass an entitlement gate. Check-in accepts an identifier that resolves to no Davi user, and that attendee holds no entitlements.

For open attendance, leave requires_ticket false and required_entitlement unset.

3. Admit people at the door

curl -X POST "https://api.davi.social/api/v1/sessions/SESSION_SLUG/admit" \
  -H "Authorization: Bearer ORG_SCOPED_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "identifier": { "type": "card", "value": "CARD_UID" } }'

identifier is the same tagged union used everywhere a person is named (card, wallet, username, user_id or link), so a door scanner can send whatever the tap gave it without resolving the person first.

One call decides and acts. decision is one of:

  • admitted: open the door.
  • needs_confirm: no ticket, but one can be issued. Redeem it and admit again.
  • blocked: blocked_reason says why.

A repeat admit answers admitted again rather than conflicting, so a double tap and a retried request are the same admission.

Who may admit somebody depends on the token:

  • Self-service: the resolved identity must match the token's subject. A person checks themselves in.
  • On behalf of: needs session:attendees:manage on an org-scoped token for the session's organization. This is the door-scanner case.

Admitting is in the expensive rate-limit bucket: 20 per minute, roughly one every three seconds. A busy entrance needs more than one credential or a queue.

Show the right refusal

A refusal at the door is a 200, not an error. /admit answers decision: "blocked" with a blocked_reason for the door to render:

blocked_reasonShow
card_frozen"This card is frozen"
already_admitted"Already here"
checkin_closed"Check-in isn't open"
too_early / too_late"Doors open at…" / "Doors have closed"
membership_required"Members only": offer to join or upgrade
prior_session_required"You need to have attended the earlier session"
at_capacity"Full"
registration_closed"No ticket, and none can be issued"

The reasons are checked in table order, so you get the most useful one to tell the person. "Already here" wins over "doors are shut". Opening hours are checked only for a first entry, so somebody re-entering a session that allows it is not turned away for arriving late. Membership and prior attendance are checked before the ticket, because no ticket fixes either.

Three responses are errors, about the request rather than the person:

StatuscodeShow
404identifier_unresolved"Card not recognized"
403forbidden_not_authorizedA staff problem, not an attendee one
409idempotency_conflictThe key was reused for a different person

4. Reward attendance automatically

Register a reward trigger instead of watching check-ins and redeeming yourself:

curl -X POST \
  "https://api.davi.social/api/v1/organizations/ORG_SLUG/reward-triggers" \
  -H "Authorization: Bearer ORG_SCOPED_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Reward 5 check-ins",
    "reward_template_uuid": "REWARD_UUID",
    "trigger_scopes": ["activity.checked_in:count:5"],
    "deduplication_window_seconds": 300
  }'

The fifth check-in now issues the reward with no call from you.

5. Update your own system

Check-ins are not delivered as webhooks. Your door already holds each decision in the admit response; to keep another system in step, read GET /sessions/{session_slug}/attendees (scope session:attendees:read) or GET /activities/{activity_slug}/attendees (scope activity:attendees:read) on a schedule and apply the difference.

For rewards, subscribe your webhook endpoint to reward.redeemed. It fires for rewards that land in a wallet belonging to a Davi user, so a reward issued to a card-only attendee does not send one. Deliveries retry, so the same event can arrive twice; use X-Webhook-Delivery to recognize a repeat.

Next