openapi: "3.0.3" info: title: Canteen Asset Tracker API version: "2.0.0" description: | REST API for Canteen Asset Geolocation Tool — asset CRUD, check-ins, customers, locations, rooms, users, geofences, visits, OCR, and CSV exports. ## Base URL `https://canteen.ourpad.casa:8901` ## Settings entities Dynamic CRUD at `/api/settings/{entity}` for: `categories`, `makes`, `models`, `key_names`, `key_types`, `badge_types`. ## Auth POST `/api/auth/login` returns a Bearer token. Most routes are currently unauthenticated but the client should attach `Authorization: Bearer `. servers: - url: https://canteen.ourpad.casa:8901 description: Production paths: # ── Health ──────────────────────────────────────────────────────────────── /health: get: summary: Server health check operationId: health responses: "200": description: OK content: application/json: schema: type: object properties: status: type: string example: ok # ── Auth ────────────────────────────────────────────────────────────────── /api/auth/login: post: summary: Login operationId: login requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/LoginRequest" responses: "200": description: JWT token content: application/json: schema: type: object properties: token: type: string user: type: object /api/auth/me: get: summary: Current user info operationId: auth_me security: - bearerAuth: [] responses: "200": description: User profile content: application/json: schema: type: object # ── Assets ──────────────────────────────────────────────────────────────── /api/assets: get: summary: List assets operationId: list_assets parameters: - name: category in: query schema: type: string responses: "200": description: Array of assets post: summary: Create asset operationId: create_asset requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/AssetCreate" responses: "201": description: Created asset /api/assets/{asset_id}: get: summary: Get asset operationId: get_asset parameters: - $ref: "#/components/parameters/AssetId" responses: "200": description: Asset object put: summary: Update asset operationId: update_asset parameters: - $ref: "#/components/parameters/AssetId" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/AssetUpdate" responses: "200": description: Updated asset delete: summary: Delete asset operationId: delete_asset parameters: - $ref: "#/components/parameters/AssetId" responses: "200": description: Deleted /api/assets/search: get: summary: Search assets by machine ID operationId: search_by_machine_id parameters: - name: machine_id in: query required: true schema: type: string responses: "200": description: Matching assets # ── Check-ins ───────────────────────────────────────────────────────────── /api/checkins: get: summary: List check-ins operationId: list_checkins parameters: - name: asset_id in: query schema: type: integer responses: "200": description: Array of check-ins post: summary: Create check-in operationId: create_checkin requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/CheckinCreate" responses: "201": description: Created check-in # ── Customers ───────────────────────────────────────────────────────────── /api/customers: get: summary: List customers operationId: list_customers responses: "200": description: Array of customers post: summary: Create customer operationId: create_customer requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/CustomerCreate" responses: "201": description: Created customer /api/customers/{cust_id}: get: summary: Get customer operationId: get_customer parameters: - $ref: "#/components/parameters/CustId" responses: "200": description: Customer object put: summary: Update customer operationId: update_customer parameters: - $ref: "#/components/parameters/CustId" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/CustomerUpdate" responses: "200": description: Updated customer delete: summary: Delete customer operationId: delete_customer parameters: - $ref: "#/components/parameters/CustId" responses: "200": description: Deleted # ── Locations ───────────────────────────────────────────────────────────── /api/locations: get: summary: List locations operationId: list_locations parameters: - name: customer_id in: query schema: type: integer responses: "200": description: Array of locations (each includes rooms array) post: summary: Create location operationId: create_location requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/LocationCreate" responses: "201": description: Created location /api/locations/{loc_id}: get: summary: Get location operationId: get_location parameters: - $ref: "#/components/parameters/LocId" responses: "200": description: Location object put: summary: Update location operationId: update_location parameters: - $ref: "#/components/parameters/LocId" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/LocationUpdate" responses: "200": description: Updated location delete: summary: Delete location operationId: delete_location parameters: - $ref: "#/components/parameters/LocId" responses: "200": description: Deleted # ── Rooms ───────────────────────────────────────────────────────────────── /api/rooms: get: summary: List rooms operationId: list_rooms parameters: - name: location_id in: query schema: type: integer responses: "200": description: Array of rooms post: summary: Create room operationId: create_room requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/RoomCreate" responses: "201": description: Created room /api/rooms/{room_id}: get: summary: Get room operationId: get_room parameters: - $ref: "#/components/parameters/RoomId" responses: "200": description: Room object put: summary: Update room operationId: update_room parameters: - $ref: "#/components/parameters/RoomId" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/RoomUpdate" responses: "200": description: Updated room delete: summary: Delete room operationId: delete_room parameters: - $ref: "#/components/parameters/RoomId" responses: "200": description: Deleted # ── Users ───────────────────────────────────────────────────────────────── /api/users: get: summary: List users operationId: list_users responses: "200": description: Array of users post: summary: Create user operationId: create_user requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/UserCreate" responses: "201": description: Created user /api/users/{user_id}: get: summary: Get user operationId: get_user parameters: - $ref: "#/components/parameters/UserId" responses: "200": description: User object put: summary: Update user operationId: update_user parameters: - $ref: "#/components/parameters/UserId" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/UserUpdate" responses: "200": description: Updated user delete: summary: Delete user operationId: delete_user parameters: - $ref: "#/components/parameters/UserId" responses: "200": description: Deleted /api/users/{user_id}/geofences: get: summary: List geofences assigned to a user (service areas) operationId: list_user_geofences parameters: - $ref: "#/components/parameters/UserId" responses: "200": description: Array of geofences assigned to this user # ── Geofences ───────────────────────────────────────────────────────────── /api/geofences: get: summary: List geofences operationId: list_geofences responses: "200": description: Array of geofences content: application/json: schema: type: array items: $ref: "#/components/schemas/GeofenceResponse" post: summary: Create geofence operationId: create_geofence requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/GeofenceCreate" responses: "201": description: Created geofence content: application/json: schema: $ref: "#/components/schemas/GeofenceResponse" /api/geofences/{geofence_id}: put: summary: Update geofence operationId: update_geofence parameters: - $ref: "#/components/parameters/GeofenceId" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/GeofenceUpdate" responses: "200": description: Updated geofence content: application/json: schema: $ref: "#/components/schemas/GeofenceResponse" delete: summary: Delete geofence operationId: delete_geofence parameters: - $ref: "#/components/parameters/GeofenceId" responses: "200": description: Deleted /api/geofences/check: post: summary: Check if point is inside a geofence operationId: check_geofence_point requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/GeofencePointCheck" responses: "200": description: Geofence check result /api/proximity: get: summary: Proximity check — find assets near a GPS point operationId: proximity_check parameters: - name: lat in: query required: true schema: type: number - name: lng in: query required: true schema: type: number - name: radius_km in: query schema: type: number default: 1.0 responses: "200": description: Nearby assets # ── Visits ──────────────────────────────────────────────────────────────── /api/visits: get: summary: List visits operationId: list_visits parameters: - name: asset_id in: query schema: type: integer responses: "200": description: Array of visits post: summary: Create visit operationId: create_visit requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/VisitCreate" responses: "201": description: Created visit /api/visits/stats: get: summary: Visit statistics operationId: get_visit_stats responses: "200": description: Visit stats # ── Activity ────────────────────────────────────────────────────────────── /api/activity: get: summary: Activity feed operationId: list_activity parameters: - name: user_id in: query schema: type: integer - name: limit in: query schema: type: integer default: 100 responses: "200": description: Activity entries # ── Stats & Exports ─────────────────────────────────────────────────────── /api/stats: get: summary: Dashboard statistics operationId: get_stats responses: "200": description: Aggregate stats /api/export/assets: get: summary: Export assets as CSV operationId: export_assets_csv responses: "200": description: CSV file content: text/csv: schema: type: string /api/export/checkins: get: summary: Export check-ins as CSV operationId: export_checkins_csv parameters: - name: asset_id in: query schema: type: integer responses: "200": description: CSV file content: text/csv: schema: type: string /api/export/service-summary: get: summary: Export service summary as CSV operationId: export_service_summary_csv responses: "200": description: CSV file content: text/csv: schema: type: string # ── Uploads & OCR ───────────────────────────────────────────────────────── /api/upload/icon: post: summary: Upload icon image operationId: upload_icon requestBody: required: true content: multipart/form-data: schema: type: object properties: file: type: string format: binary responses: "201": description: Uploaded icon path /api/upload/photo: post: summary: Upload photo operationId: upload_photo requestBody: required: true content: multipart/form-data: schema: type: object properties: file: type: string format: binary responses: "201": description: Uploaded photo path /api/ocr: post: summary: OCR a sticker/image — extract machine ID operationId: ocr_sticker requestBody: required: true content: multipart/form-data: schema: type: object properties: file: type: string format: binary responses: "200": description: Extracted text # ── Settings (dynamic) ──────────────────────────────────────────────────── /api/settings/{entity}: get: summary: List settings entities operationId: list_settings parameters: - name: entity in: path required: true schema: type: string enum: [categories, makes, models, key_names, key_types, badge_types] responses: "200": description: Array of settings values post: summary: Create settings entity operationId: create_setting parameters: - name: entity in: path required: true schema: type: string enum: [categories, makes, models, key_names, key_types, badge_types] requestBody: required: true content: application/json: schema: type: object properties: name: type: string responses: "201": description: Created entity /api/settings/{entity}/{eid}: get: summary: Get settings entity operationId: get_setting parameters: - name: entity in: path required: true schema: type: string enum: [categories, makes, models, key_names, key_types, badge_types] - $ref: "#/components/parameters/SettingId" responses: "200": description: Settings entity object put: summary: Update settings entity operationId: update_setting parameters: - name: entity in: path required: true schema: type: string enum: [categories, makes, models, key_names, key_types, badge_types] - $ref: "#/components/parameters/SettingId" requestBody: required: true content: application/json: schema: type: object properties: name: type: string responses: "200": description: Updated entity delete: summary: Delete settings entity operationId: delete_setting parameters: - name: entity in: path required: true schema: type: string enum: [categories, makes, models, key_names, key_types, badge_types] - $ref: "#/components/parameters/SettingId" responses: "204": description: Deleted # ── Components ────────────────────────────────────────────────────────────── components: securitySchemes: bearerAuth: type: http scheme: bearer parameters: AssetId: name: asset_id in: path required: true schema: type: integer CustId: name: cust_id in: path required: true schema: type: integer LocId: name: loc_id in: path required: true schema: type: integer RoomId: name: room_id in: path required: true schema: type: integer UserId: name: user_id in: path required: true schema: type: integer GeofenceId: name: geofence_id in: path required: true schema: type: integer SettingId: name: eid in: path required: true schema: type: integer schemas: AssetKey: type: object properties: key_name: type: string key_type: type: string AssetBadge: type: object properties: badge_name: type: string AssetCreate: type: object required: - machine_id - name properties: machine_id: type: string name: type: string serial_number: type: string description: type: string category: type: string enum: [Furniture, Appliances, "Utensils & Serveware", Equipment, Other] default: Other status: type: string enum: [active, maintenance, retired] default: active make: type: string model: type: string address: type: string building_name: type: string building_number: type: string floor: type: string room: type: string trailer_number: type: string walking_directions: type: string map_link: type: string parking_location: type: string photo_path: type: string customer_id: type: integer location_id: type: integer assigned_to: type: integer latitude: type: number longitude: type: number geofence_radius_meters: type: integer default: 50 keys: type: array items: $ref: "#/components/schemas/AssetKey" badges: type: array items: type: string AssetUpdate: type: object properties: machine_id: type: string name: type: string serial_number: type: string description: type: string category: type: string status: type: string make: type: string model: type: string address: type: string building_name: type: string building_number: type: string floor: type: string room: type: string trailer_number: type: string walking_directions: type: string map_link: type: string parking_location: type: string photo_path: type: string customer_id: type: integer location_id: type: integer assigned_to: type: integer latitude: type: number longitude: type: number geofence_radius_meters: type: integer keys: type: array items: $ref: "#/components/schemas/AssetKey" badges: type: array items: type: string CheckinCreate: type: object required: - asset_id properties: asset_id: type: integer latitude: type: number longitude: type: number accuracy: type: number photo_path: type: string notes: type: string user_id: type: integer CustomerContact: type: object properties: name: type: string phone: type: string email: type: string CustomerCreate: type: object required: - name properties: name: type: string contacts: type: array items: $ref: "#/components/schemas/CustomerContact" CustomerUpdate: type: object properties: name: type: string contacts: type: array items: $ref: "#/components/schemas/CustomerContact" LoginRequest: type: object required: - username - password properties: username: type: string password: type: string LocationCreate: type: object required: - name properties: customer_id: type: integer name: type: string address: type: string building_name: type: string building_number: type: string floor: type: string trailer_number: type: string site_hours: type: string access_notes: type: string walking_directions: type: string map_link: type: string latitude: type: number longitude: type: number LocationUpdate: type: object properties: customer_id: type: integer name: type: string address: type: string building_name: type: string building_number: type: string floor: type: string trailer_number: type: string site_hours: type: string access_notes: type: string walking_directions: type: string map_link: type: string latitude: type: number longitude: type: number RoomCreate: type: object required: - location_id - name properties: location_id: type: integer name: type: string floor: type: string RoomUpdate: type: object properties: name: type: string floor: type: string location_id: type: integer UserCreate: type: object required: - username - password properties: username: type: string password: type: string role: type: string enum: [technician, admin] UserUpdate: type: object properties: role: type: string password: type: string GeofenceCreate: type: object required: - name - points properties: name: type: string points: type: array items: type: object properties: lat: type: number lng: type: number color: type: string default: "#3388ff" user_ids: type: array items: type: integer description: IDs of users assigned to this service area GeofenceResponse: type: object properties: id: type: integer name: type: string points: type: array color: type: string created_at: type: string updated_at: type: string assigned_users: type: array items: $ref: "#/components/schemas/UserBrief" UserBrief: type: object properties: id: type: integer username: type: string role: type: string GeofenceUpdate: type: object properties: name: type: string points: type: array color: type: string user_ids: type: array items: type: integer description: Replace assigned users for this service area GeofencePointCheck: type: object required: - lat - lng properties: lat: type: number lng: type: number VisitCreate: type: object required: - asset_id properties: user_id: type: integer asset_id: type: integer latitude: type: number longitude: type: number duration_minutes: type: integer