app.zeens.event

zeens.app

{
  "$type": "com.atproto.lexicon.schema",
  "defs": {
    "itineraryItem": {
      "properties": {
        "location": {
          "description": "Optional location for this item. Only meaningful for events with mode 'in_person' or 'hybrid'. Items with lat/lng are plotted on the event map.",
          "ref": "#location",
          "type": "ref"
        },
        "text": {
          "description": "What happens at this point in the event (e.g. 'Meet at the park entrance', 'Darkroom demo', 'Q&A with the curator').",
          "maxLength": 500,
          "type": "string"
        },
        "time": {
          "description": "Free-text time label for this item (e.g. '9:00 AM', 'Lunch'). Stored as text, not a datetime, so it renders verbatim without timezone handling.",
          "maxLength": 50,
          "type": "string"
        }
      },
      "required": [
        "text"
      ],
      "type": "object"
    },
    "link": {
      "properties": {
        "label": {
          "maxLength": 100,
          "type": "string"
        },
        "uri": {
          "format": "uri",
          "type": "string"
        }
      },
      "required": [
        "uri"
      ],
      "type": "object"
    },
    "location": {
      "properties": {
        "address": {
          "description": "Human-readable street address.",
          "maxLength": 500,
          "type": "string"
        },
        "lat": {
          "description": "Latitude in microdegrees (degrees x 1e7). ATProto rejects non-integer numbers, so coordinates are scaled to integers.",
          "type": "integer"
        },
        "lng": {
          "description": "Longitude in microdegrees (degrees x 1e7).",
          "type": "integer"
        },
        "name": {
          "description": "Venue / place name.",
          "maxLength": 200,
          "type": "string"
        }
      },
      "type": "object"
    },
    "main": {
      "description": "An event: a photowalk, workshop, meetup, exhibition or talk. Written to the organiser's own PDS. Others RSVP via app.zeens.rsvp (ATProto handle) or via an email address tracked off-protocol in PocketBase. The app does NOT collect payment: paid events carry free-text paymentInstructions and any URL/link in that field is rejected at write time. Carries the app.zeens.v marker so firehose + Lua filters recognise it. NOTE: the record id stays 'app.zeens.event' and the app.zeens.* namespace is network plumbing — it is not renamed when the product is. Two orthogonal axes describe an event: `mode` (WHERE it happens — in person, online, or both) and `categories` (WHAT it is — one or more of photowalk/workshop/meetup/exhibition/talk/other). The `hostedBy` field is DEPRECATED (the organiser is the record owner); kept for back-compat with old records.",
      "key": "tid",
      "record": {
        "properties": {
          "app.zeens.v": {
            "description": "Zeens version marker (always 1).",
            "type": "integer"
          },
          "approvalRequired": {
            "description": "When true, an RSVP is pending until the organiser approves it; only approved RSVPs count toward the cap and the attendee list. Approval state is tracked off-protocol (the organiser cannot mutate another user's rsvp record). Default false.",
            "type": "boolean"
          },
          "capacityMode": {
            "description": "Behaviour when maxParticipants is reached. 'hard' = block new going RSVPs. 'waitlist' = overflow goes on a waitlist, promoted in RSVP order. Only meaningful when maxParticipants is set. Default 'hard'.",
            "knownValues": [
              "hard",
              "waitlist"
            ],
            "type": "string"
          },
          "categories": {
            "description": "WHAT the event is. One or more (max 3) — an event can be both a photowalk and a meetup. Drives browse/filter, which OR-matches any element. Orthogonal to `mode`. The legacy value 'online_meetup' is tolerated on read and means mode 'online' + category 'meetup'.",
            "items": {
              "knownValues": [
                "photowalk",
                "workshop",
                "meetup",
                "exhibition",
                "talk",
                "other"
              ],
              "type": "string"
            },
            "maxLength": 3,
            "minLength": 1,
            "type": "array"
          },
          "coverImage": {
            "accept": [
              "image/jpeg",
              "image/png",
              "image/webp"
            ],
            "description": "Wide cover image shown on the event header and share previews.",
            "maxSize": 4000000,
            "type": "blob"
          },
          "createdAt": {
            "format": "datetime",
            "type": "string"
          },
          "createdBy": {
            "description": "Display text for the event creator/organiser (the account that owns this record).",
            "maxLength": 200,
            "type": "string"
          },
          "description": {
            "description": "Long-form event description.",
            "maxLength": 3000,
            "type": "string"
          },
          "endsAt": {
            "description": "Optional event end instant.",
            "format": "datetime",
            "type": "string"
          },
          "facets": {
            "description": "Rich-text facets (links, mentions) over the description, reusing the Bluesky richtext lexicon.",
            "items": {
              "ref": "app.bsky.richtext.facet",
              "type": "ref"
            },
            "type": "array"
          },
          "hostedBy": {
            "description": "DEPRECATED. Display text for who is hosting. The organiser is the record owner, so this is no longer written; kept so old records still read.",
            "maxLength": 200,
            "type": "string"
          },
          "itinerary": {
            "description": "Ordered agenda for the event: a list of items, each with optional time, descriptive text, and an optional location. Works for any event type — a photowalk's route, a workshop's schedule, an exhibition's programme. Items that carry coordinates are drawn on the event map as numbered pins in order; an event whose items have no coordinates simply renders the list with no map.",
            "items": {
              "ref": "#itineraryItem",
              "type": "ref"
            },
            "maxLength": 30,
            "type": "array"
          },
          "links": {
            "description": "Social / website links for the event.",
            "items": {
              "ref": "#link",
              "type": "ref"
            },
            "maxLength": 5,
            "type": "array"
          },
          "location": {
            "description": "Physical location. Expected for mode 'in_person' and 'hybrid'; absent for 'online'.",
            "ref": "#location",
            "type": "ref"
          },
          "maxParticipants": {
            "description": "Optional cap on confirmed (going) participants. Absent = unlimited.",
            "minimum": 1,
            "type": "integer"
          },
          "mode": {
            "description": "WHERE the event happens, and one of exactly three values. 'in_person' needs a location; 'online' needs a virtualUrl and has no location; 'hybrid' needs both. Orthogonal to `categories` — an online workshop is mode 'online' + categories ['workshop']. Records written before this field carried meaning default to 'in_person' on read. The legacy value 'virtual' is tolerated on read and means 'online'.",
            "knownValues": [
              "in_person",
              "online",
              "hybrid"
            ],
            "type": "string"
          },
          "paid": {
            "description": "Whether the event costs money. The app never processes payment.",
            "type": "boolean"
          },
          "paymentInstructions": {
            "description": "Free-text explanation of how the organiser collects payment out of band. MUST NOT contain a URL/link; rejected at write time client- and server-side.",
            "maxLength": 1000,
            "type": "string"
          },
          "photoCategories": {
            "description": "Photography genre tags (Street, Landscape, Portrait, ...). Up to 3, optional. Drawn from the shared category list used by posts. Distinct from `categories`, which is the event type.",
            "items": {
              "maxLength": 100,
              "type": "string"
            },
            "maxLength": 3,
            "type": "array"
          },
          "rsvpPolicy": {
            "description": "Who may RSVP. 'atmosphere' = any ATProto account. 'photosky' = members only. 'anyone_email' = anyone, including non-ATProto people via an email address. Enforcement is soft (obscurity-level), like collection visibility.",
            "knownValues": [
              "atmosphere",
              "photosky",
              "anyone_email"
            ],
            "type": "string"
          },
          "startsAt": {
            "description": "Event start, as an absolute instant. Render in `timezone`.",
            "format": "datetime",
            "type": "string"
          },
          "timezone": {
            "description": "IANA timezone name (e.g. 'America/New_York'). Stored separately from startsAt so calendar export (.ics DTSTART;TZID) and the displayed local time are correct.",
            "maxLength": 100,
            "type": "string"
          },
          "title": {
            "description": "Event title.",
            "maxGraphemes": 150,
            "maxLength": 300,
            "type": "string"
          },
          "virtualUrl": {
            "description": "Join link for events with mode 'online' or 'hybrid'. Required for those modes. Shown only to RSVP'd users in the client.",
            "format": "uri",
            "type": "string"
          }
        },
        "required": [
          "title",
          "startsAt",
          "timezone",
          "mode",
          "rsvpPolicy",
          "categories",
          "createdAt",
          "app.zeens.v"
        ],
        "type": "object"
      },
      "type": "record"
    }
  },
  "id": "app.zeens.event",
  "lexicon": 1
}

Validate Record

Validate a record against app.zeens.event

Validation Options
Automatically resolve and include external schemas for full validation
Treat any remaining unresolved references as valid

Metadata

DID
did:plc:pwpuqwge6g7xrjmfbw73uycw
CID
bafyreib7avfkz7tzud2bhhqcbl5e2n4xoix6icaa2ndhlbc2ccq6iucxau
Indexed At
2026-10-22 16:31 UTC
AT-URI
at://did:plc:pwpuqwge6g7xrjmfbw73uycw/com.atproto.lexicon.schema/app.zeens.event

Referenced Schemas (1)

Similar Lexicons

Lexicons whose schemas describe something close to this one. Check them before publishing a new one that overlaps.

Lexicon Garden

@