openapi: 3.0.0 info: title: City Council API version: 1.0.0 description: API for OpenCouncil paths: # Cities /api/cities: get: summary: Get cities based on user permissions description: Returns cities with full data including counts. Respects user authorization when includeUnlisted=true. parameters: - name: includeUnlisted in: query description: When true, includes unlisted cities the user can administer schema: type: boolean default: false responses: '200': description: Successful response content: application/json: schema: type: array items: $ref: '#/components/schemas/CityWithCounts' description: Full city data with counts '400': description: Invalid query parameters content: application/json: schema: $ref: '#/components/schemas/ValidationErrorResponse' '401': description: Not authorized to view unlisted cities content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Failed to fetch cities /api/cities/all: get: summary: Get all cities in minimal format description: Returns all non-pending cities in minimal format. No authentication required. Ideal for dropdowns and selectors. responses: '200': description: Successful response content: application/json: schema: type: array items: $ref: '#/components/schemas/CityMinimal' description: All cities in minimal format '500': description: Failed to fetch cities /api/cities/{cityId}: get: summary: Get a specific city parameters: - $ref: '#/components/parameters/cityId' responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/CityWithCounts' '404': description: City not found '500': description: Failed to fetch city # Meetings /api/cities/{cityId}/meetings: get: summary: Get meetings for a city parameters: - $ref: '#/components/parameters/cityId' - name: limit in: query description: Maximum number of meetings to return (1-100) schema: type: integer minimum: 1 maximum: 100 responses: '200': description: Successful response content: application/json: schema: type: array items: $ref: '#/components/schemas/Meeting' '400': description: Invalid query parameters content: application/json: schema: $ref: '#/components/schemas/ValidationErrorResponse' '500': description: Failed to fetch meetings /api/cities/{cityId}/meetings/{meetingId}: get: summary: Get transcript for a specific meeting parameters: - $ref: '#/components/parameters/cityId' - $ref: '#/components/parameters/meetingId' responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/Transcript' '404': description: Transcript not found '500': description: Failed to fetch transcript # Parties /api/cities/{cityId}/parties: get: summary: Get parties for a city parameters: - $ref: '#/components/parameters/cityId' responses: '200': description: Successful response content: application/json: schema: type: array items: $ref: '#/components/schemas/Party' '500': description: Failed to fetch parties /api/cities/{cityId}/parties/{partyId}: get: summary: Get a specific party parameters: - $ref: '#/components/parameters/cityId' - $ref: '#/components/parameters/partyId' responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/Party' '404': description: Party not found '500': description: Failed to fetch party # People /api/cities/{cityId}/people: get: summary: Get people for a city parameters: - $ref: '#/components/parameters/cityId' responses: '200': description: Successful response content: application/json: schema: type: array items: $ref: '#/components/schemas/Person' '500': description: Failed to fetch people /api/cities/{cityId}/people/{personId}: get: summary: Get a specific person parameters: - $ref: '#/components/parameters/cityId' - $ref: '#/components/parameters/personId' responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/Person' '404': description: Person not found '500': description: Failed to fetch person # Search /api/search: post: summary: Search subjects description: Search for subjects using a hybrid search approach that combines traditional text search with semantic search requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SearchRequest' responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/SearchResponse' '400': description: Invalid request parameters content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Internal server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' components: parameters: cityId: name: cityId in: path required: true schema: type: string meetingId: name: meetingId in: path required: true schema: type: string partyId: name: partyId in: path required: true schema: type: string personId: name: personId in: path required: true schema: type: string schemas: CityMinimal: type: object description: Base city data with minimal fields properties: id: type: string name: type: string name_en: type: string name_municipality: type: string name_municipality_en: type: string logoImage: type: string nullable: true supportsNotifications: type: boolean status: type: string enum: [pending, unlisted, listed] officialSupport: type: boolean authorityType: type: string enum: [municipality, region] City: allOf: - $ref: '#/components/schemas/CityMinimal' - type: object description: Full city data from the database properties: timezone: type: string wikipediaId: type: string nullable: true createdAt: type: string format: date-time updatedAt: type: string format: date-time CityWithCounts: allOf: - $ref: '#/components/schemas/City' - type: object description: City data with count information properties: _count: type: object description: Aggregated counts for this city properties: persons: type: integer description: Number of people in this city parties: type: integer description: Number of parties in this city councilMeetings: type: integer description: Number of released meetings in this city Meeting: type: object properties: id: type: string name: type: string name_en: type: string dateTime: type: string format: date-time youtubeUrl: type: string videoUrl: type: string nullable: true audioUrl: type: string nullable: true muxPlaybackId: type: string nullable: true released: type: boolean cityId: type: string administrativeBody: $ref: '#/components/schemas/AdministrativeBody' nullable: true subjects: type: array items: $ref: '#/components/schemas/Subject' createdAt: type: string format: date-time updatedAt: type: string format: date-time Transcript: type: array items: $ref: '#/components/schemas/SpeakerSegment' SpeakerSegment: type: object properties: id: type: string startTimestamp: type: number endTimestamp: type: number meeting: $ref: '#/components/schemas/Meeting' person: $ref: '#/components/schemas/Person' nullable: true text: type: string summary: type: object nullable: true properties: text: type: string SpeakerTag: type: object properties: id: type: string label: type: string person: $ref: '#/components/schemas/Person' Utterance: type: object properties: id: type: string startTimestamp: type: number endTimestamp: type: number text: type: string drift: type: number words: type: array items: $ref: '#/components/schemas/Word' Word: type: object properties: id: type: string text: type: string startTimestamp: type: number endTimestamp: type: number confidence: type: number TopicLabel: type: object properties: id: type: string topic: $ref: '#/components/schemas/Topic' Topic: type: object properties: id: type: string name: type: string name_en: type: string colorHex: type: string Summary: type: object properties: id: type: string text: type: string Party: type: object properties: id: type: string name: type: string name_en: type: string name_short: type: string name_short_en: type: string colorHex: type: string logo: type: string nullable: true cityId: type: string createdAt: type: string format: date-time updatedAt: type: string format: date-time Person: type: object properties: id: type: string name: type: string name_en: type: string name_short: type: string name_short_en: type: string image: type: string nullable: true role: type: string nullable: true role_en: type: string nullable: true activeFrom: type: string format: date-time nullable: true activeTo: type: string format: date-time nullable: true cityId: type: string partyId: type: string nullable: true createdAt: type: string format: date-time updatedAt: type: string format: date-time SearchRequest: type: object required: - query properties: query: type: string description: The search query example: "ηλεκτρικά πατίνια" cityIds: type: array items: type: string description: Array of city IDs to filter by example: ["athens", "chania"] personIds: type: array items: type: string description: Array of person IDs to filter by partyIds: type: array items: type: string description: Array of party IDs to filter by topicIds: type: array items: type: string description: Array of topic IDs to filter by dateRange: type: object description: Date range for filtering results properties: start: type: string format: date-time description: Start date (ISO 8601) example: "2024-01-01T00:00:00Z" end: type: string format: date-time description: End date (ISO 8601) example: "2024-12-31T23:59:59Z" location: type: object description: Geographic location filter properties: point: type: object description: Center point coordinates properties: lat: type: number description: Latitude lon: type: number description: Longitude radius: type: number description: Search radius in kilometers default: 5 page: type: integer description: Page number default: 1 minimum: 1 pageSize: type: integer description: Results per page default: 10 minimum: 1 maximum: 100 detailed: type: boolean description: Whether to return detailed results with speaker segment text default: false SearchResultLight: type: object properties: id: type: string name: type: string description: type: string councilMeeting: allOf: - $ref: '#/components/schemas/Meeting' - type: object properties: city: $ref: '#/components/schemas/City' topic: $ref: '#/components/schemas/Topic' nullable: true introducedBy: $ref: '#/components/schemas/Person' nullable: true location: type: object properties: id: type: string type: type: string enum: [point, lineString, polygon] text: type: string coordinates: type: object properties: x: type: number y: type: number nullable: true score: type: number matchedSpeakerSegmentIds: type: array items: type: string description: Array of speaker segment IDs that matched the search query nullable: true highlights: type: array items: $ref: '#/components/schemas/Highlight' SearchResultDetailed: allOf: - $ref: '#/components/schemas/SearchResultLight' - type: object properties: speakerSegments: type: array items: $ref: '#/components/schemas/SpeakerSegment' SearchResponse: type: object properties: results: oneOf: - type: array items: $ref: '#/components/schemas/SearchResultLight' - type: array items: $ref: '#/components/schemas/SearchResultDetailed' pagination: type: object properties: total: type: integer description: Total number of results page: type: integer description: Current page number pageSize: type: integer description: Number of results per page totalPages: type: integer description: Total number of pages ErrorResponse: type: object properties: error: type: object properties: code: type: string enum: [INVALID_REQUEST, SEARCH_ERROR, RATE_LIMIT_EXCEEDED, INTERNAL_ERROR] message: type: string details: type: object Subject: type: object properties: id: type: string name: type: string description: type: string nullable: true hot: type: boolean agendaItemIndex: type: integer nullable: true topic: $ref: '#/components/schemas/Topic' nullable: true speakerSegments: type: array items: type: object properties: id: type: string createdAt: type: string format: date-time updatedAt: type: string format: date-time AdministrativeBody: type: object properties: id: type: string name: type: string name_en: type: string type: type: string enum: [council, committee, community] cityId: type: string createdAt: type: string format: date-time updatedAt: type: string format: date-time ValidationErrorResponse: type: object properties: error: type: array items: type: object properties: code: type: string message: type: string path: type: array items: oneOf: - type: string - type: number