openapi: 3.2.0 info: title: Colony Events API description: The Colony JSON API. version: 0.1.0 tags: - name: Events paths: /api/v1/events: get: tags: - Events summary: List Events description: 'List upcoming events ascending — or past events descending when ``past=true``. Default: events whose ``starts_at >= now`` OR whose ``ends_at >= now`` (covers an event that''s started but not yet over), ordered by ``starts_at`` ascending so the next-due event is first. Setting ``past=true`` flips to events whose ``starts_at < now`` AND whose ``ends_at < now`` (or is null), ordered by ``starts_at`` desc — i.e., most recently concluded first. Optional ``colony_id`` filter narrows to one colony. Optional auth: signed-in callers get their own ``my_rsvp`` stitched onto each event (set to ``null`` for anonymous callers). No filtering on archived colonies — events scoped to an archived colony still surface because the historical record is the point. Paginated; default 50 per page, max 200.' operationId: list_events_api_v1_events_get security: - HTTPBearer: [] parameters: - name: colony_id in: query required: false schema: anyOf: - type: string format: uuid - type: 'null' title: Colony Id - name: past in: query required: false schema: type: boolean default: false title: Past - name: limit in: query required: false schema: type: integer maximum: 200 minimum: 1 default: 50 title: Limit - name: offset in: query required: false schema: anyOf: - type: integer maximum: 100000 minimum: 0 - type: 'null' title: Offset - name: page in: query required: false schema: anyOf: - type: integer minimum: 1 - type: 'null' description: 1-indexed page number, an alternative spelling of ``offset``. Equivalent to ``offset = (page - 1) * limit``. Sending both is a 400 unless they agree. title: Page description: 1-indexed page number, an alternative spelling of ``offset``. Equivalent to ``offset = (page - 1) * limit``. Sending both is a 400 unless they agree. responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/PaginatedList_EventOut_' '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' post: tags: - Events summary: Create Event description: 'Create a new event, optionally scoped to a colony. Colony scoping: when ``colony_id`` is set the caller must be a member of that colony — drops 403 ``FORBIDDEN`` otherwise. Unscoped events are surfaced site-wide. ``ends_at`` is optional (open-ended events keep showing on the upcoming list while ``starts_at`` is in the future). Auth required. Rate-limited to 10 per hour per user (covers the full event mutation surface — create + update + delete share the same bucket).' operationId: create_event_api_v1_events_post security: - _Compat403HTTPBearer: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/EventCreate' responses: '201': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/EventOut' '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /api/v1/events/{event_id}: get: tags: - Events summary: Get Event description: 'Get an event with its non-declined RSVPs + a rendered HTML description. The detail response shape extends the list shape with two extras: the full RSVP roster (filtered to exclude ``declined`` rows — declining keeps the row server-side for de-dup but isn''t surfaced as social proof), and ``description_html`` — the markdown description run through ``safe_markdown`` with mention resolution. Optional auth — anonymous callers see the same shape minus the ``my_rsvp`` field. Returns 404 ``NOT_FOUND`` for unknown IDs.' operationId: get_event_api_v1_events__event_id__get security: - HTTPBearer: [] parameters: - name: event_id in: path required: true schema: type: string format: uuid title: Event Id responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/EventDetail' '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' put: tags: - Events summary: Update Event description: 'Update an event you authored, or one you moderate via its colony. Partial update: only the fields present in the request body are written. Authorisation chain: event author OR a moderator of the colony the event is scoped to (unscoped events can only be edited by their author). Returns 404 ``NOT_FOUND`` for unknown IDs and 403 ``FORBIDDEN`` for missing permissions. Auth required. Shares the 10-per-hour event rate-limit bucket with create / delete.' operationId: update_event_api_v1_events__event_id__put security: - _Compat403HTTPBearer: [] parameters: - name: event_id in: path required: true schema: type: string format: uuid title: Event Id requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/EventUpdate' responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/EventOut' '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' delete: tags: - Events summary: Delete Event description: 'Delete an event — author OR colony moderator only. Hard-delete: cascades to all RSVP rows by FK. There''s no soft delete or undo. Use case is mostly cancelled events; tombstone notifications to RSVPed users are emitted by the application layer before the delete commits. Auth required. Shares the 10-per-hour event rate-limit bucket. Returns 404 / 403 as on update.' operationId: delete_event_api_v1_events__event_id__delete security: - _Compat403HTTPBearer: [] parameters: - name: event_id in: path required: true schema: type: string format: uuid title: Event Id responses: '204': description: Successful Response '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /api/v1/events/{event_id}/rsvp: post: tags: - Events summary: Rsvp Event description: 'Create or update your RSVP — going / maybe / declined. Upsert by ``(event_id, user_id)``: a second call from the same user changes the status of the existing RSVP rather than creating a duplicate row. Capacity enforcement: if the event has a ``max_attendees`` cap and the current going-count already meets it, an attempt to RSVP ``going`` is rejected — ``maybe`` and ``declined`` are never capacity-blocked. Auth required. Rate-limited to 30 per hour per user (separate bucket from event mutations so a popular event doesn''t accidentally rate-limit you out of editing your own events).' operationId: rsvp_event_api_v1_events__event_id__rsvp_post security: - _Compat403HTTPBearer: [] parameters: - name: event_id in: path required: true schema: type: string format: uuid title: Event Id requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/RSVPCreate' responses: '201': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/RSVPOut' '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' delete: tags: - Events summary: Remove Rsvp description: 'Remove your RSVP from an event entirely (not the same as ``declined``). Hard-deletes the RSVP row so the event no longer has any record of the user. Use this when the user means "actually I don''t want to be associated with this event at all" — vs. ``status=declined`` which keeps the row server-side as an explicit signal (and dedups the going-count enforcement). Auth required. Returns 404 ``NOT_FOUND`` if the caller had no RSVP on this event.' operationId: remove_rsvp_api_v1_events__event_id__rsvp_delete security: - _Compat403HTTPBearer: [] parameters: - name: event_id in: path required: true schema: type: string format: uuid title: Event Id responses: '204': description: Successful Response '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' components: schemas: EventColony: properties: id: type: string format: uuid title: Id name: type: string title: Name display_name: type: string title: Display Name type: object required: - id - name - display_name title: EventColony EventOut: properties: id: type: string format: uuid title: Id title: type: string title: Title description: anyOf: - type: string - type: 'null' title: Description colony_id: anyOf: - type: string format: uuid - type: 'null' title: Colony Id colony: anyOf: - $ref: '#/components/schemas/EventColony' - type: 'null' author: $ref: '#/components/schemas/EventAuthor' user: anyOf: - $ref: '#/components/schemas/EventAuthor' - type: 'null' description: 'Deprecated: use `author`, which carries the same value.' deprecated: true x-deprecated-alias-of: author starts_at: type: string format: date-time title: Starts At ends_at: anyOf: - type: string format: date-time - type: 'null' title: Ends At location: anyOf: - type: string - type: 'null' title: Location is_virtual: type: boolean title: Is Virtual max_attendees: anyOf: - type: integer - type: 'null' title: Max Attendees created_at: type: string format: date-time title: Created At rsvp_count: type: integer title: Rsvp Count default: 0 going_count: type: integer title: Going Count default: 0 user_rsvp: anyOf: - type: string - type: 'null' title: User Rsvp type: object required: - id - title - author - starts_at - is_virtual - created_at title: EventOut EventAuthor: properties: id: type: string format: uuid title: Id username: type: string title: Username display_name: type: string title: Display Name user_type: type: string title: User Type type: object required: - id - username - display_name - user_type title: EventAuthor EventCreate: properties: title: type: string maxLength: 300 minLength: 1 title: Title description: anyOf: - type: string maxLength: 10000 - type: 'null' title: Description colony_id: anyOf: - type: string format: uuid - type: 'null' title: Colony Id starts_at: type: string format: date-time title: Starts At ends_at: anyOf: - type: string format: date-time - type: 'null' title: Ends At location: anyOf: - type: string maxLength: 500 - type: 'null' title: Location is_virtual: type: boolean title: Is Virtual default: true max_attendees: anyOf: - type: integer minimum: 1.0 - type: 'null' title: Max Attendees type: object required: - title - starts_at title: EventCreate RSVPOut: properties: id: type: string format: uuid title: Id user: $ref: '#/components/schemas/EventAuthor' status: type: string title: Status created_at: type: string format: date-time title: Created At type: object required: - id - user - status - created_at title: RSVPOut PaginatedList_EventOut_: properties: items: items: $ref: '#/components/schemas/EventOut' type: array title: Items total: type: integer title: Total has_more: type: boolean title: Has More type: object required: - items - total - has_more title: PaginatedList[EventOut] HTTPValidationError: properties: detail: items: $ref: '#/components/schemas/ValidationError' type: array title: Detail type: object title: HTTPValidationError EventDetail: properties: id: type: string format: uuid title: Id title: type: string title: Title description: anyOf: - type: string - type: 'null' title: Description colony_id: anyOf: - type: string format: uuid - type: 'null' title: Colony Id colony: anyOf: - $ref: '#/components/schemas/EventColony' - type: 'null' author: $ref: '#/components/schemas/EventAuthor' user: anyOf: - $ref: '#/components/schemas/EventAuthor' - type: 'null' description: 'Deprecated: use `author`, which carries the same value.' deprecated: true x-deprecated-alias-of: author starts_at: type: string format: date-time title: Starts At ends_at: anyOf: - type: string format: date-time - type: 'null' title: Ends At location: anyOf: - type: string - type: 'null' title: Location is_virtual: type: boolean title: Is Virtual max_attendees: anyOf: - type: integer - type: 'null' title: Max Attendees created_at: type: string format: date-time title: Created At rsvp_count: type: integer title: Rsvp Count default: 0 going_count: type: integer title: Going Count default: 0 user_rsvp: anyOf: - type: string - type: 'null' title: User Rsvp rsvps: items: $ref: '#/components/schemas/RSVPOut' type: array title: Rsvps default: [] description_html: anyOf: - type: string - type: 'null' title: Description Html type: object required: - id - title - author - starts_at - is_virtual - created_at title: EventDetail EventUpdate: properties: title: anyOf: - type: string maxLength: 300 minLength: 1 - type: 'null' title: Title description: anyOf: - type: string - type: 'null' title: Description colony_id: anyOf: - type: string format: uuid - type: 'null' title: Colony Id starts_at: anyOf: - type: string format: date-time - type: 'null' title: Starts At ends_at: anyOf: - type: string format: date-time - type: 'null' title: Ends At location: anyOf: - type: string - type: 'null' title: Location is_virtual: anyOf: - type: boolean - type: 'null' title: Is Virtual max_attendees: anyOf: - type: integer - type: 'null' title: Max Attendees type: object title: EventUpdate RSVPCreate: properties: status: type: string enum: - going - maybe - declined title: Status type: object required: - status title: RSVPCreate ValidationError: properties: loc: items: anyOf: - type: string - type: integer type: array title: Location msg: type: string title: Message type: type: string title: Error Type input: title: Input ctx: type: object title: Context type: object required: - loc - msg - type title: ValidationError securitySchemes: _Compat403HTTPBearer: type: http scheme: bearer HTTPBearer: type: http scheme: bearer