You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2945 lines
77 KiB
YAML

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

openapi: 3.0.3
info:
title: Polotsk Transit API
version: 1.0.0-draft
description: |
Черновик OpenAPI 3.0, восстановленный по реальному коду проекта.
В проекте нет встроенного swagger/openapi генератора, поэтому часть схем описана
по фактическим SQL-запросам и runtime-поведению контроллеров.
servers:
- url: http://localhost:3000
description: Local backend
tags:
- name: System
- name: Auth
- name: Upload
- name: Routes
- name: Route Stops
- name: Schedules
- name: Holidays
- name: Stops
- name: ETA
- name: Sync
- name: Alerts
- name: Users
- name: Telemetry
- name: Realtime
paths:
/health:
get:
tags: [System]
summary: Health check
security: []
responses:
'200':
description: Service status
content:
application/json:
schema:
type: object
required: [status, timestamp, version]
properties:
status:
type: string
example: healthy
timestamp:
type: string
format: date-time
version:
type: string
example: 1.0.0
/api/v1/auth/login:
post:
tags: [Auth]
summary: Login
security: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/LoginRequest'
responses:
'200':
description: Authenticated
content:
application/json:
schema:
$ref: '#/components/schemas/LoginResponse'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
/api/v1/auth/refresh:
post:
tags: [Auth]
summary: Refresh access token
security: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/RefreshRequest'
responses:
'200':
description: New access token
content:
application/json:
schema:
$ref: '#/components/schemas/RefreshResponse'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
/api/v1/auth/logout:
post:
tags: [Auth]
summary: Logout by refresh token
security: []
requestBody:
required: false
content:
application/json:
schema:
$ref: '#/components/schemas/LogoutRequest'
responses:
'200':
description: Logged out
content:
application/json:
schema:
$ref: '#/components/schemas/MessageResponse'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
/api/v1/auth/me:
get:
tags: [Auth]
summary: Current user
security:
- BearerAuth: []
responses:
'200':
description: Current authenticated user
content:
application/json:
schema:
$ref: '#/components/schemas/UserMe'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'500':
$ref: '#/components/responses/InternalError'
/api/v1/auth/change-password:
post:
tags: [Auth]
summary: Change password
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ChangePasswordRequest'
responses:
'200':
description: Password changed
content:
application/json:
schema:
$ref: '#/components/schemas/MessageResponse'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'500':
$ref: '#/components/responses/InternalError'
/api/v1/upload/avatar:
post:
tags: [Upload]
summary: Upload or replace current user avatar
security:
- BearerAuth: []
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required: [avatar]
properties:
avatar:
type: string
format: binary
responses:
'200':
description: Avatar uploaded
content:
application/json:
schema:
$ref: '#/components/schemas/AvatarUploadResponse'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
delete:
tags: [Upload]
summary: Delete current user avatar
security:
- BearerAuth: []
responses:
'200':
description: Avatar deleted
content:
application/json:
schema:
$ref: '#/components/schemas/MessageResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
/api/v1/upload/avatar/{userId}:
delete:
tags: [Upload]
summary: Admin deletes any user avatar
security:
- BearerAuth: []
parameters:
- $ref: '#/components/parameters/UserId'
responses:
'200':
description: Avatar deleted
content:
application/json:
schema:
$ref: '#/components/schemas/MessageResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
/api/v1/routes:
get:
tags: [Routes]
summary: List active routes
security:
- BearerAuth: []
- ApiKeyAuth: []
responses:
'200':
description: Route list
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/RouteSummary'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
post:
tags: [Routes]
summary: Create route
security:
- BearerAuth: []
- ApiKeyAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateRouteRequest'
responses:
'201':
description: Route created
content:
application/json:
schema:
$ref: '#/components/schemas/Route'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
/api/v1/routes/{id}:
get:
tags: [Routes]
summary: Get route detail with stops
security:
- BearerAuth: []
- ApiKeyAuth: []
parameters:
- $ref: '#/components/parameters/Id'
responses:
'200':
description: Route detail
content:
application/json:
schema:
$ref: '#/components/schemas/RouteDetail'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
put:
tags: [Routes]
summary: Update route
security:
- BearerAuth: []
- ApiKeyAuth: []
parameters:
- $ref: '#/components/parameters/Id'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateRouteRequest'
responses:
'200':
description: Updated route
content:
application/json:
schema:
$ref: '#/components/schemas/Route'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
delete:
tags: [Routes]
summary: Delete route
security:
- BearerAuth: []
- ApiKeyAuth: []
parameters:
- $ref: '#/components/parameters/Id'
responses:
'200':
description: Route deleted
content:
application/json:
schema:
$ref: '#/components/schemas/MessageResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
/api/v1/routes/{routeId}/stops:
get:
tags: [Route Stops]
summary: Get ordered stops for route
security:
- BearerAuth: []
- ApiKeyAuth: []
parameters:
- $ref: '#/components/parameters/RouteId'
responses:
'200':
description: Route stop list
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/RouteStopView'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
post:
tags: [Route Stops]
summary: Add stop to route
security:
- BearerAuth: []
- ApiKeyAuth: []
parameters:
- $ref: '#/components/parameters/RouteId'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/AddRouteStopRequest'
responses:
'201':
description: Route stop created
content:
application/json:
schema:
$ref: '#/components/schemas/RouteStopRecord'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
/api/v1/routes/{routeId}/available-stops:
get:
tags: [Route Stops]
summary: Stops not yet assigned to route
security:
- BearerAuth: []
- ApiKeyAuth: []
parameters:
- $ref: '#/components/parameters/RouteId'
responses:
'200':
description: Available stops
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/AvailableStop'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
/api/v1/routes/{routeId}/stops/{routeStopId}:
put:
tags: [Route Stops]
summary: Update route stop position or offset
security:
- BearerAuth: []
- ApiKeyAuth: []
parameters:
- $ref: '#/components/parameters/RouteId'
- $ref: '#/components/parameters/RouteStopId'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateRouteStopRequest'
responses:
'200':
description: Updated route stop
content:
application/json:
schema:
$ref: '#/components/schemas/RouteStopRecord'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
delete:
tags: [Route Stops]
summary: Remove stop from route
security:
- BearerAuth: []
- ApiKeyAuth: []
parameters:
- $ref: '#/components/parameters/RouteId'
- $ref: '#/components/parameters/RouteStopId'
responses:
'200':
description: Route stop deleted
content:
application/json:
schema:
$ref: '#/components/schemas/MessageResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
/api/v1/routes/{routeId}/stops-reorder:
put:
tags: [Route Stops]
summary: Bulk reorder route stops
security:
- BearerAuth: []
- ApiKeyAuth: []
parameters:
- $ref: '#/components/parameters/RouteId'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ReorderRouteStopsRequest'
responses:
'200':
description: Route stops reordered
content:
application/json:
schema:
type: object
required: [message, count]
properties:
message:
type: string
count:
type: integer
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
/api/v1/routes/{routeId}/schedules:
get:
tags: [Schedules]
summary: List route schedules
security:
- BearerAuth: []
- ApiKeyAuth: []
parameters:
- $ref: '#/components/parameters/RouteId'
responses:
'200':
description: Schedules for route
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/ScheduleWithRoute'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
/api/v1/routes/{routeId}/schedule/today:
get:
tags: [Schedules]
summary: Get active schedule for date
security:
- BearerAuth: []
- ApiKeyAuth: []
parameters:
- $ref: '#/components/parameters/RouteId'
- in: query
name: date
schema:
type: string
format: date
required: false
responses:
'200':
description: Schedule for date
content:
application/json:
schema:
$ref: '#/components/schemas/TodayScheduleResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
/api/v1/routes/{routeId}/schedules/copy:
post:
tags: [Schedules]
summary: Copy schedule from one day type to another
security:
- BearerAuth: []
- ApiKeyAuth: []
parameters:
- $ref: '#/components/parameters/RouteId'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CopyScheduleRequest'
responses:
'200':
description: Existing schedule updated
content:
application/json:
schema:
$ref: '#/components/schemas/Schedule'
'201':
description: New schedule created
content:
application/json:
schema:
$ref: '#/components/schemas/Schedule'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
/api/v1/schedules:
post:
tags: [Schedules]
summary: Create schedule
security:
- BearerAuth: []
- ApiKeyAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateScheduleRequest'
responses:
'201':
description: Schedule created
content:
application/json:
schema:
$ref: '#/components/schemas/Schedule'
'400':
$ref: '#/components/responses/BadRequest'
'409':
description: Schedule already exists
content:
application/json:
schema:
type: object
properties:
error:
type: string
message:
type: string
existingId:
type: integer
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
/api/v1/schedules/{id}:
get:
tags: [Schedules]
summary: Get schedule by id
security:
- BearerAuth: []
- ApiKeyAuth: []
parameters:
- $ref: '#/components/parameters/Id'
responses:
'200':
description: Schedule detail
content:
application/json:
schema:
$ref: '#/components/schemas/ScheduleWithRoute'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
put:
tags: [Schedules]
summary: Update schedule
security:
- BearerAuth: []
- ApiKeyAuth: []
parameters:
- $ref: '#/components/parameters/Id'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateScheduleRequest'
responses:
'200':
description: Schedule updated
content:
application/json:
schema:
$ref: '#/components/schemas/Schedule'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
delete:
tags: [Schedules]
summary: Delete schedule
security:
- BearerAuth: []
- ApiKeyAuth: []
parameters:
- $ref: '#/components/parameters/Id'
responses:
'200':
description: Schedule deleted
content:
application/json:
schema:
$ref: '#/components/schemas/MessageResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
/api/v1/holidays:
get:
tags: [Holidays]
summary: List holidays
security:
- BearerAuth: []
- ApiKeyAuth: []
parameters:
- in: query
name: year
schema:
type: integer
required: false
responses:
'200':
description: Holiday list
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Holiday'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
post:
tags: [Holidays]
summary: Create holiday
security:
- BearerAuth: []
- ApiKeyAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateHolidayRequest'
responses:
'201':
description: Holiday created
content:
application/json:
schema:
$ref: '#/components/schemas/Holiday'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
/api/v1/holidays/upcoming:
get:
tags: [Holidays]
summary: Get upcoming holidays
security:
- BearerAuth: []
- ApiKeyAuth: []
parameters:
- in: query
name: limit
schema:
type: integer
required: false
responses:
'200':
description: Upcoming holidays
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/UpcomingHoliday'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
/api/v1/holidays/year/{year}:
get:
tags: [Holidays]
summary: Get holidays for year with resolved recurring dates
security:
- BearerAuth: []
- ApiKeyAuth: []
parameters:
- in: path
name: year
required: true
schema:
type: integer
responses:
'200':
description: Holidays for year
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/HolidayForYear'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
/api/v1/holidays/check/{date}:
get:
tags: [Holidays]
summary: Check if date is holiday
security:
- BearerAuth: []
- ApiKeyAuth: []
parameters:
- in: path
name: date
required: true
schema:
type: string
format: date
responses:
'200':
description: Holiday check
content:
application/json:
schema:
type: object
required: [date, isHoliday, holidayName]
properties:
date:
type: string
format: date
isHoliday:
type: boolean
holidayName:
type: string
nullable: true
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
/api/v1/holidays/{id}:
put:
tags: [Holidays]
summary: Update holiday
security:
- BearerAuth: []
- ApiKeyAuth: []
parameters:
- $ref: '#/components/parameters/Id'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateHolidayRequest'
responses:
'200':
description: Holiday updated
content:
application/json:
schema:
$ref: '#/components/schemas/Holiday'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
delete:
tags: [Holidays]
summary: Delete holiday
security:
- BearerAuth: []
- ApiKeyAuth: []
parameters:
- $ref: '#/components/parameters/Id'
responses:
'200':
description: Holiday deleted
content:
application/json:
schema:
$ref: '#/components/schemas/MessageResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
/api/v1/stops:
get:
tags: [Stops]
summary: List active stops
security:
- BearerAuth: []
- ApiKeyAuth: []
responses:
'200':
description: Stop list
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/StopSummary'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
post:
tags: [Stops]
summary: Create stop
security:
- BearerAuth: []
- ApiKeyAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateStopRequest'
responses:
'201':
description: Stop created
content:
application/json:
schema:
$ref: '#/components/schemas/Stop'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
/api/v1/stops/nearby:
get:
tags: [Stops]
summary: Find nearby stops
security:
- BearerAuth: []
- ApiKeyAuth: []
parameters:
- in: query
name: lat
required: true
schema:
type: number
- in: query
name: lon
required: true
schema:
type: number
- in: query
name: radius
required: false
schema:
type: number
default: 500
responses:
'200':
description: Nearby stops
content:
application/json:
schema:
type: array
items:
allOf:
- $ref: '#/components/schemas/Stop'
- type: object
properties:
distance_meters:
type: number
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
/api/v1/stops/{id}:
get:
tags: [Stops]
summary: Get stop detail
security:
- BearerAuth: []
- ApiKeyAuth: []
parameters:
- $ref: '#/components/parameters/Id'
responses:
'200':
description: Stop detail
content:
application/json:
schema:
$ref: '#/components/schemas/StopDetail'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
put:
tags: [Stops]
summary: Update stop
security:
- BearerAuth: []
- ApiKeyAuth: []
parameters:
- $ref: '#/components/parameters/Id'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateStopRequest'
responses:
'200':
description: Stop updated
content:
application/json:
schema:
$ref: '#/components/schemas/Stop'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
delete:
tags: [Stops]
summary: Delete stop
security:
- BearerAuth: []
- ApiKeyAuth: []
parameters:
- $ref: '#/components/parameters/Id'
responses:
'200':
description: Stop deleted
content:
application/json:
schema:
$ref: '#/components/schemas/MessageResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
/api/v1/eta/calculate:
post:
tags: [ETA]
summary: Calculate ETA for route+stop
security:
- BearerAuth: []
- ApiKeyAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CalculateEtaRequest'
responses:
'200':
description: ETA calculated
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/CalculateEtaResponse'
- type: object
properties:
message:
type: string
nextDay:
type: string
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
/api/v1/eta/stop/{stopId}:
get:
tags: [ETA]
summary: ETA for all routes at stop
security:
- BearerAuth: []
- ApiKeyAuth: []
parameters:
- in: path
name: stopId
required: true
schema:
type: integer
responses:
'200':
description: ETA list
content:
application/json:
schema:
$ref: '#/components/schemas/StopEtasResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
/api/v1/sync:
get:
tags: [Sync]
summary: Full or incremental sync
security:
- BearerAuth: []
- ApiKeyAuth: []
parameters:
- in: query
name: lastSync
required: false
schema:
type: string
format: date-time
responses:
'200':
description: Sync payload
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/FullSyncResponse'
- $ref: '#/components/schemas/IncrementalSyncResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
/api/v1/sync/status:
get:
tags: [Sync]
summary: Sync status
security:
- BearerAuth: []
- ApiKeyAuth: []
responses:
'200':
description: Status payload
content:
application/json:
schema:
$ref: '#/components/schemas/SyncStatusResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
/api/v1/alerts:
get:
tags: [Alerts]
summary: List alerts
security:
- BearerAuth: []
- ApiKeyAuth: []
parameters:
- in: query
name: route_id
required: false
schema:
type: integer
- in: query
name: is_active
required: false
schema:
type: boolean
- in: query
name: alert_type
required: false
schema:
$ref: '#/components/schemas/AlertType'
responses:
'200':
description: Alert list
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/AlertRead'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
post:
tags: [Alerts]
summary: Create alert
security:
- BearerAuth: []
- ApiKeyAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateAlertRequest'
responses:
'201':
description: Alert created
content:
application/json:
schema:
$ref: '#/components/schemas/Alert'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
/api/v1/alerts/active:
get:
tags: [Alerts]
summary: List currently active alerts
security:
- BearerAuth: []
- ApiKeyAuth: []
parameters:
- in: query
name: route_id
required: false
schema:
type: integer
responses:
'200':
description: Active alerts
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/AlertRead'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
/api/v1/alerts/cleanup:
post:
tags: [Alerts]
summary: Deactivate expired alerts
security:
- BearerAuth: []
- ApiKeyAuth: []
responses:
'200':
description: Cleanup stats
content:
application/json:
schema:
type: object
required: [message, deactivated_count]
properties:
message:
type: string
deactivated_count:
type: integer
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
/api/v1/alerts/{id}:
get:
tags: [Alerts]
summary: Get alert by id
security:
- BearerAuth: []
- ApiKeyAuth: []
parameters:
- $ref: '#/components/parameters/Id'
responses:
'200':
description: Alert detail
content:
application/json:
schema:
$ref: '#/components/schemas/AlertRead'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
put:
tags: [Alerts]
summary: Update alert
security:
- BearerAuth: []
- ApiKeyAuth: []
parameters:
- $ref: '#/components/parameters/Id'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateAlertRequest'
responses:
'200':
description: Alert updated
content:
application/json:
schema:
$ref: '#/components/schemas/Alert'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
delete:
tags: [Alerts]
summary: Delete alert
security:
- BearerAuth: []
- ApiKeyAuth: []
parameters:
- $ref: '#/components/parameters/Id'
responses:
'200':
description: Alert deleted
content:
application/json:
schema:
$ref: '#/components/schemas/MessageResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
/api/v1/users:
get:
tags: [Users]
summary: List users
security:
- BearerAuth: []
responses:
'200':
description: User list
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/UserAdminView'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
post:
tags: [Users]
summary: Create user
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateUserRequest'
responses:
'201':
description: User created
content:
application/json:
schema:
$ref: '#/components/schemas/UserCreated'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
/api/v1/users/{id}:
get:
tags: [Users]
summary: Get user by id
security:
- BearerAuth: []
parameters:
- $ref: '#/components/parameters/Id'
responses:
'200':
description: User detail
content:
application/json:
schema:
$ref: '#/components/schemas/UserAdminView'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
put:
tags: [Users]
summary: Update user
security:
- BearerAuth: []
parameters:
- $ref: '#/components/parameters/Id'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateUserRequest'
responses:
'200':
description: User updated
content:
application/json:
schema:
$ref: '#/components/schemas/UserUpdated'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
delete:
tags: [Users]
summary: Delete user
security:
- BearerAuth: []
parameters:
- $ref: '#/components/parameters/Id'
responses:
'200':
description: User deleted
content:
application/json:
schema:
$ref: '#/components/schemas/MessageResponse'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
/api/v1/users/{id}/reset-password:
post:
tags: [Users]
summary: Reset user password
security:
- BearerAuth: []
parameters:
- $ref: '#/components/parameters/Id'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ResetPasswordRequest'
responses:
'200':
description: Password reset
content:
application/json:
schema:
$ref: '#/components/schemas/MessageResponse'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
/api/v1/telemetry/ingest:
post:
tags: [Telemetry]
summary: Ingest GPS telemetry
security:
- BearerAuth: []
- ApiKeyAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/TelemetryIngestRequest'
example:
routeId: 7
vehicleNumber: DEMO-7A
vehicleType: bus
lat: 55.4869
lon: 28.7856
speedKmh: 24
heading: 90
accuracyMeters: 5
recordedAt: '2026-04-16T10:30:00.000Z'
responses:
'201':
description: Telemetry accepted
content:
application/json:
schema:
$ref: '#/components/schemas/TelemetryIngestResponse'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
/api/v1/realtime/routes/{routeId}/vehicles:
get:
tags: [Realtime]
summary: Live vehicles snapshot for route
security:
- BearerAuth: []
- ApiKeyAuth: []
parameters:
- in: path
name: routeId
required: true
schema:
type: integer
- in: query
name: includeStale
schema:
type: boolean
- in: query
name: staleAfterSeconds
schema:
type: integer
default: 180
responses:
'200':
description: Route live vehicles
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/LiveVehicleState'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
/api/v1/realtime/vehicles/{vehicleId}:
get:
tags: [Realtime]
summary: Live vehicle state
security:
- BearerAuth: []
- ApiKeyAuth: []
parameters:
- in: path
name: vehicleId
required: true
schema:
type: integer
- in: query
name: staleAfterSeconds
schema:
type: integer
default: 180
responses:
'200':
description: Vehicle live state
content:
application/json:
schema:
$ref: '#/components/schemas/LiveVehicleState'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalError'
components:
securitySchemes:
ApiKeyAuth:
type: apiKey
in: header
name: X-API-Key
BearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
parameters:
Id:
in: path
name: id
required: true
schema:
type: integer
RouteId:
in: path
name: routeId
required: true
schema:
type: integer
RouteStopId:
in: path
name: routeStopId
required: true
schema:
type: integer
UserId:
in: path
name: userId
required: true
schema:
type: integer
responses:
BadRequest:
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
Unauthorized:
description: Authentication required or invalid credentials
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
Forbidden:
description: Forbidden
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
NotFound:
description: Not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
TooManyRequests:
description: Too many requests
content:
application/json:
schema:
type: object
properties:
error:
type: string
message:
type: string
retryAfter:
type: integer
InternalError:
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
schemas:
ErrorResponse:
type: object
properties:
error:
type: string
message:
type: string
MessageResponse:
type: object
required: [message]
properties:
message:
type: string
RouteType:
type: string
enum: [bus, minibus, trolleybus, tram]
ScheduleDayType:
type: string
enum: [weekday, saturday, sunday, holiday]
AlertType:
type: string
enum: [delay, cancellation, detour, info]
UserRole:
type: string
enum: [admin, user]
Route:
type: object
properties:
id:
type: integer
route_number:
type: string
name:
type: string
type:
$ref: '#/components/schemas/RouteType'
color:
type: string
is_active:
type: boolean
description:
type: string
nullable: true
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
RouteSummary:
allOf:
- $ref: '#/components/schemas/Route'
- type: object
properties:
stops_count:
oneOf:
- type: integer
- type: string
Stop:
type: object
properties:
id:
type: integer
name:
type: string
location:
description: Raw PostGIS geography serialization; exact shape is not guaranteed
nullable: true
latitude:
type: number
longitude:
type: number
address:
type: string
nullable: true
description:
type: string
nullable: true
is_active:
type: boolean
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
StopSummary:
allOf:
- $ref: '#/components/schemas/Stop'
- type: object
properties:
routes_count:
oneOf:
- type: integer
- type: string
StopOnRoute:
allOf:
- $ref: '#/components/schemas/Stop'
- type: object
properties:
sequence:
type: integer
time_offset_minutes:
type: integer
RouteDetail:
allOf:
- $ref: '#/components/schemas/Route'
- type: object
properties:
stops:
type: array
items:
$ref: '#/components/schemas/StopOnRoute'
RouteAtStop:
allOf:
- $ref: '#/components/schemas/Route'
- type: object
properties:
sequence:
type: integer
time_offset_minutes:
type: integer
StopDetail:
allOf:
- $ref: '#/components/schemas/Stop'
- type: object
properties:
routes:
type: array
items:
$ref: '#/components/schemas/RouteAtStop'
RouteStopRecord:
type: object
properties:
id:
type: integer
route_id:
type: integer
stop_id:
type: integer
sequence:
type: integer
time_offset_minutes:
type: integer
RouteStopView:
type: object
properties:
route_stop_id:
type: integer
sequence:
type: integer
time_offset_minutes:
type: integer
stop_id:
type: integer
name:
type: string
address:
type: string
nullable: true
latitude:
type: number
longitude:
type: number
AvailableStop:
type: object
properties:
id:
type: integer
name:
type: string
address:
type: string
nullable: true
latitude:
type: number
longitude:
type: number
Schedule:
type: object
properties:
id:
type: integer
route_id:
type: integer
day_type:
$ref: '#/components/schemas/ScheduleDayType'
departure_times:
type: array
items:
type: string
valid_from:
type: string
format: date
nullable: true
valid_until:
type: string
format: date
nullable: true
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
ScheduleWithRoute:
allOf:
- $ref: '#/components/schemas/Schedule'
- type: object
properties:
route_number:
type: string
route_name:
type: string
TodayScheduleResponse:
type: object
properties:
date:
type: string
format: date
scheduleType:
$ref: '#/components/schemas/ScheduleDayType'
isHoliday:
type: boolean
holidayName:
type: string
nullable: true
schedule:
$ref: '#/components/schemas/ScheduleWithRoute'
Holiday:
type: object
properties:
id:
type: integer
date:
type: string
format: date
name:
type: string
is_recurring:
type: boolean
recurring_month:
type: integer
nullable: true
recurring_day:
type: integer
nullable: true
created_at:
type: string
format: date-time
HolidayForYear:
type: object
properties:
id:
type: integer
date:
type: string
format: date
name:
type: string
is_recurring:
type: boolean
UpcomingHoliday:
type: object
properties:
id:
type: integer
name:
type: string
next_date:
type: string
format: date
is_recurring:
type: boolean
Alert:
type: object
properties:
id:
type: integer
route_id:
type: integer
nullable: true
alert_type:
$ref: '#/components/schemas/AlertType'
title:
type: string
message:
type: string
start_time:
type: string
format: date-time
end_time:
type: string
format: date-time
nullable: true
is_active:
type: boolean
created_at:
type: string
format: date-time
AlertRead:
allOf:
- $ref: '#/components/schemas/Alert'
- type: object
properties:
route_name:
type: string
nullable: true
route_number:
type: string
nullable: true
color:
type: string
nullable: true
UserLoginPayload:
type: object
properties:
id:
type: integer
username:
type: string
full_name:
type: string
nullable: true
email:
type: string
nullable: true
role:
$ref: '#/components/schemas/UserRole'
is_active:
type: boolean
last_login:
type: string
format: date-time
nullable: true
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
avatar_url:
type: string
nullable: true
UserMe:
type: object
properties:
id:
type: integer
username:
type: string
full_name:
type: string
nullable: true
email:
type: string
nullable: true
role:
$ref: '#/components/schemas/UserRole'
avatar_url:
type: string
nullable: true
last_login:
type: string
format: date-time
nullable: true
created_at:
type: string
format: date-time
UserAdminView:
type: object
properties:
id:
type: integer
username:
type: string
full_name:
type: string
nullable: true
email:
type: string
nullable: true
role:
$ref: '#/components/schemas/UserRole'
is_active:
type: boolean
last_login:
type: string
format: date-time
nullable: true
created_at:
type: string
format: date-time
UserCreated:
type: object
properties:
id:
type: integer
username:
type: string
full_name:
type: string
nullable: true
email:
type: string
nullable: true
role:
$ref: '#/components/schemas/UserRole'
is_active:
type: boolean
created_at:
type: string
format: date-time
UserUpdated:
type: object
properties:
id:
type: integer
username:
type: string
full_name:
type: string
nullable: true
email:
type: string
nullable: true
role:
$ref: '#/components/schemas/UserRole'
is_active:
type: boolean
Arrival:
type: object
properties:
scheduledTime:
type: string
estimatedTime:
type: string
minutesUntil:
type: integer
delay:
type: integer
CalculateEtaResponse:
type: object
properties:
routeId:
type: integer
stopId:
type: integer
currentTime:
type: string
dayType:
$ref: '#/components/schemas/ScheduleDayType'
arrivals:
type: array
items:
$ref: '#/components/schemas/Arrival'
StopEtasRoute:
type: object
properties:
id:
type: integer
route_number:
type: string
name:
type: string
type:
$ref: '#/components/schemas/RouteType'
color:
type: string
arrivals:
type: array
items:
$ref: '#/components/schemas/Arrival'
error:
type: string
nullable: true
StopEtasResponse:
type: object
properties:
stopId:
oneOf:
- type: integer
- type: string
routes:
type: array
items:
$ref: '#/components/schemas/StopEtasRoute'
FullSyncResponse:
type: object
properties:
syncType:
type: string
enum: [full]
timestamp:
type: string
format: date-time
version:
type: string
data:
type: object
properties:
routes:
type: array
items:
$ref: '#/components/schemas/Route'
stops:
type: array
items:
$ref: '#/components/schemas/Stop'
routeStops:
type: array
items:
$ref: '#/components/schemas/RouteStopRecord'
schedules:
type: array
items:
$ref: '#/components/schemas/Schedule'
alerts:
type: array
items:
$ref: '#/components/schemas/Alert'
holidays:
type: array
items:
$ref: '#/components/schemas/Holiday'
metadata:
type: object
properties:
routesCount:
type: integer
stopsCount:
type: integer
schedulesCount:
type: integer
holidaysCount:
type: integer
IncrementalSyncResponse:
type: object
properties:
syncType:
type: string
enum: [incremental]
timestamp:
type: string
format: date-time
lastSync:
type: string
format: date-time
changes:
type: object
properties:
routes:
$ref: '#/components/schemas/SyncChangeBucketRoutes'
stops:
$ref: '#/components/schemas/SyncChangeBucketStops'
routeStops:
$ref: '#/components/schemas/SyncChangeBucketRouteStops'
schedules:
$ref: '#/components/schemas/SyncChangeBucketSchedules'
alerts:
type: array
items:
$ref: '#/components/schemas/Alert'
metadata:
type: object
properties:
changesCount:
type: integer
SyncChangeBucketRoutes:
type: object
properties:
updated:
type: array
items:
$ref: '#/components/schemas/Route'
deleted:
type: array
items:
type: integer
SyncChangeBucketStops:
type: object
properties:
updated:
type: array
items:
$ref: '#/components/schemas/Stop'
deleted:
type: array
items:
type: integer
SyncChangeBucketRouteStops:
type: object
properties:
updated:
type: array
items:
$ref: '#/components/schemas/RouteStopRecord'
deleted:
type: array
items:
type: integer
SyncChangeBucketSchedules:
type: object
properties:
updated:
type: array
items:
$ref: '#/components/schemas/Schedule'
deleted:
type: array
items:
type: integer
SyncStatusResponse:
type: object
properties:
status:
type: string
timestamp:
type: string
format: date-time
database:
type: object
properties:
routes_count:
oneOf:
- type: integer
- type: string
stops_count:
oneOf:
- type: integer
- type: string
schedules_count:
oneOf:
- type: integer
- type: string
last_change:
type: string
format: date-time
nullable: true
LoginRequest:
type: object
required: [username, password]
properties:
username:
type: string
password:
type: string
LoginResponse:
type: object
properties:
user:
$ref: '#/components/schemas/UserLoginPayload'
accessToken:
type: string
refreshToken:
type: string
RefreshRequest:
type: object
required: [refreshToken]
properties:
refreshToken:
type: string
RefreshResponse:
type: object
required: [accessToken]
properties:
accessToken:
type: string
LogoutRequest:
type: object
properties:
refreshToken:
type: string
ChangePasswordRequest:
type: object
required: [currentPassword, newPassword]
properties:
currentPassword:
type: string
newPassword:
type: string
minLength: 6
refreshToken:
type: string
CreateRouteRequest:
type: object
required: [route_number, name, type]
properties:
route_number:
type: string
name:
type: string
type:
$ref: '#/components/schemas/RouteType'
color:
type: string
default: '#0066CC'
description:
type: string
UpdateRouteRequest:
type: object
properties:
route_number:
type: string
name:
type: string
type:
$ref: '#/components/schemas/RouteType'
color:
type: string
description:
type: string
is_active:
type: boolean
CreateStopRequest:
type: object
required: [name, latitude, longitude]
properties:
name:
type: string
latitude:
type: number
longitude:
type: number
address:
type: string
description:
type: string
UpdateStopRequest:
type: object
properties:
name:
type: string
latitude:
type: number
longitude:
type: number
address:
type: string
description:
type: string
is_active:
type: boolean
AddRouteStopRequest:
type: object
required: [stopId]
properties:
stopId:
type: integer
sequence:
type: integer
timeOffsetMinutes:
type: integer
default: 0
UpdateRouteStopRequest:
type: object
properties:
sequence:
type: integer
timeOffsetMinutes:
type: integer
ReorderRouteStopsRequest:
type: object
required: [stops]
properties:
stops:
type: array
items:
type: object
required: [stopId, sequence]
properties:
stopId:
type: integer
sequence:
type: integer
timeOffsetMinutes:
type: integer
CreateScheduleRequest:
type: object
required: [routeId, dayType, departureTimes]
properties:
routeId:
type: integer
dayType:
$ref: '#/components/schemas/ScheduleDayType'
departureTimes:
type: array
items:
type: string
validFrom:
type: string
format: date
nullable: true
validUntil:
type: string
format: date
nullable: true
UpdateScheduleRequest:
type: object
properties:
departureTimes:
type: array
items:
type: string
validFrom:
type: string
format: date
nullable: true
validUntil:
type: string
format: date
nullable: true
CopyScheduleRequest:
type: object
required: [fromDayType, toDayType]
properties:
fromDayType:
$ref: '#/components/schemas/ScheduleDayType'
toDayType:
$ref: '#/components/schemas/ScheduleDayType'
CreateHolidayRequest:
type: object
required: [date, name]
properties:
date:
type: string
format: date
name:
type: string
isRecurring:
type: boolean
UpdateHolidayRequest:
type: object
properties:
date:
type: string
format: date
name:
type: string
isRecurring:
type: boolean
CreateAlertRequest:
type: object
required: [alert_type, title, message, start_time]
properties:
route_id:
type: integer
nullable: true
alert_type:
$ref: '#/components/schemas/AlertType'
title:
type: string
message:
type: string
start_time:
type: string
format: date-time
end_time:
type: string
format: date-time
nullable: true
is_active:
type: boolean
UpdateAlertRequest:
type: object
properties:
route_id:
type: integer
nullable: true
alert_type:
$ref: '#/components/schemas/AlertType'
title:
type: string
message:
type: string
start_time:
type: string
format: date-time
end_time:
type: string
format: date-time
nullable: true
is_active:
type: boolean
CreateUserRequest:
type: object
required: [username, password]
properties:
username:
type: string
password:
type: string
minLength: 6
full_name:
type: string
email:
type: string
format: email
role:
$ref: '#/components/schemas/UserRole'
UpdateUserRequest:
type: object
properties:
full_name:
type: string
email:
type: string
format: email
role:
$ref: '#/components/schemas/UserRole'
is_active:
type: boolean
ResetPasswordRequest:
type: object
required: [newPassword]
properties:
newPassword:
type: string
minLength: 6
AvatarUploadResponse:
type: object
properties:
message:
type: string
avatar_url:
type: string
user:
type: object
properties:
id:
type: integer
username:
type: string
avatar_url:
type: string
nullable: true
CalculateEtaRequest:
type: object
required: [stopId, routeId]
properties:
stopId:
type: integer
routeId:
type: integer
TelemetryIngestRequest:
type: object
required: [lat, lon]
properties:
vehicleId:
type: integer
nullable: true
vehicleNumber:
type: string
nullable: true
routeId:
type: integer
nullable: true
vehicleType:
$ref: '#/components/schemas/RouteType'
lat:
type: number
minimum: -90
maximum: 90
lon:
type: number
minimum: -180
maximum: 180
speedKmh:
type: number
nullable: true
heading:
type: number
minimum: 0
maximum: 360
nullable: true
accuracyMeters:
type: number
nullable: true
recordedAt:
type: string
format: date-time
nullable: true
sourceType:
type: string
nullable: true
sourceRef:
type: string
nullable: true
LiveVehicleState:
type: object
properties:
vehicle_id:
type: integer
vehicle_number:
type: string
nullable: true
registration:
type: string
nullable: true
vehicle_type:
$ref: '#/components/schemas/RouteType'
capacity:
type: integer
nullable: true
route_id:
type: integer
route_number:
type: string
route_name:
type: string
route_color:
type: string
nullable: true
latitude:
type: number
longitude:
type: number
speed_kmh:
type: number
nullable: true
heading:
type: number
nullable: true
accuracy_meters:
type: number
nullable: true
source_type:
type: string
nullable: true
source_ref:
type: string
nullable: true
last_seen_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
is_online:
type: boolean
seconds_since_update:
type: integer
is_moving:
type: boolean
TelemetryIngestResponse:
type: object
properties:
message:
type: string
vehicle:
$ref: '#/components/schemas/LiveVehicleState'