# TieMeasureFlow API Reference ## Overview TieMeasureFlow provides a REST API built with FastAPI for managing measurement tasks, recipes, and statistical analysis. The API uses API Key authentication and supports both single and batch measurement creation. **Base URL:** `http://localhost:8000/api` **API Version:** 0.1.0 ## Table of Contents 1. [Authentication](#authentication) 2. [Authorization & Roles](#authorization--roles) 3. [Rate Limiting](#rate-limiting) 4. [Response Format](#response-format) 5. [Error Codes](#error-codes) 6. [Endpoints](#endpoints) - [Auth](#auth) - [Users](#users) - [Recipes](#recipes) - [Tasks & Subtasks](#tasks--subtasks) - [Measurements](#measurements) - [Files](#files) - [Settings](#settings) - [Statistics](#statistics) - [Reports](#reports) 7. [Pagination](#pagination) 8. [Filtering](#filtering) ## Authentication ### Login Flow 1. **POST `/auth/login`** - Obtain API Key ```json { "username": "operator1", "password": "password123" } ``` Response: ```json { "user": { "id": 1, "username": "operator1", "display_name": "Operator One", "email": "op1@example.com", "roles": ["MeasurementTec"], "is_admin": false, "active": true, "language_pref": "it", "theme_pref": "light" }, "api_key": "tmf_abc123def456ghi789jkl" } ``` 2. **Include API Key in Subsequent Requests** ``` X-API-Key: tmf_abc123def456ghi789jkl ``` 3. **POST `/auth/logout`** - Invalidate API Key - Requires `X-API-Key` header - Returns HTTP 204 No Content on success ### Example: Complete Auth Flow ```bash # Login curl -X POST http://localhost:8000/api/auth/login \ -H "Content-Type: application/json" \ -d '{"username": "operator1", "password": "password123"}' # Response contains api_key: "tmf_..." # Use API key in subsequent requests curl -X GET http://localhost:8000/api/auth/me \ -H "X-API-Key: tmf_..." # Logout curl -X POST http://localhost:8000/api/auth/logout \ -H "X-API-Key: tmf_..." ``` ## Authorization & Roles All API endpoints require authentication via `X-API-Key` header. Most endpoints require specific roles: | Role | Permissions | |------|-------------| | **Maker** | Create/edit recipes, tasks, subtasks, upload files | | **MeasurementTec** | Execute measurements, barcode lookup, export CSV | | **Metrologist** | View SPC statistics, capability indices, control charts | | **Admin** | Manage users, system settings, regenerate API keys | Roles are combinable - a user can have multiple roles (stored as JSON array in database). ## Rate Limiting Rate limits are enforced per IP address using a sliding window algorithm: | Endpoint | Limit | Window | |----------|-------|--------| | `/auth/login` | 5 requests | 60 seconds | | All other endpoints | 100 requests | 60 seconds | **Error Response on Rate Limit Exceeded (HTTP 429):** ```json { "detail": "Too many login attempts. Please try again later." } ``` The response includes a `Retry-After` header indicating the number of seconds to wait: ``` Retry-After: 42 ``` ## Response Format ### Success Response All successful responses return JSON with appropriate HTTP status codes: ```json { "id": 1, "username": "operator1", "display_name": "Operator One", ... } ``` ### Error Response ```json { "detail": "Invalid username or password" } ``` ## Error Codes | Code | Meaning | Common Causes | |------|---------|---------------| | **400** | Bad Request | Invalid input, validation error, file size exceeded | | **401** | Unauthorized | Missing/invalid API key, invalid credentials | | **403** | Forbidden | User lacks required role for operation | | **404** | Not Found | Resource does not exist | | **409** | Conflict | Resource conflict (duplicate username, editing non-current recipe version) | | **422** | Unprocessable Entity | Validation error (invalid role, marker number already exists) | | **429** | Too Many Requests | Rate limit exceeded | | **500** | Internal Server Error | Unexpected server error | ## Endpoints ### Health Check #### GET `/health` Health check endpoint - no authentication required. **Response:** ```json { "status": "ok", "service": "TieMeasureFlow API", "version": "0.1.0" } ``` --- ### Auth #### POST `/auth/login` Login with username and password to receive an API key. **Request:** ```json { "username": "string", "password": "string" } ``` **Response:** HTTP 200 with `LoginResponse` ```json { "user": { ... }, "api_key": "tmf_..." } ``` **Rate Limit:** 5 requests/min **Errors:** - 401: Invalid username or password - 429: Rate limit exceeded --- #### GET `/auth/me` Get current user profile. **Headers:** ``` X-API-Key: tmf_... ``` **Response:** HTTP 200 with `UserResponse` **Errors:** - 401: Invalid/missing API key --- #### PUT `/auth/me` Update own profile (display_name, language_pref, theme_pref). **Headers:** ``` X-API-Key: tmf_... ``` **Request:** ```json { "display_name": "New Display Name", "language_pref": "en", "theme_pref": "dark" } ``` **Response:** HTTP 200 with updated `UserResponse` **Errors:** - 401: Invalid/missing API key --- #### POST `/auth/logout` Invalidate the current API key. **Headers:** ``` X-API-Key: tmf_... ``` **Response:** HTTP 204 No Content **Errors:** - 401: Invalid/missing API key --- ### Users All user management endpoints require **Admin** role. #### GET `/users` List all users. **Headers:** ``` X-API-Key: tmf_... (Admin required) ``` **Response:** HTTP 200 with `list[UserResponse]` --- #### POST `/users` Create a new user. **Headers:** ``` X-API-Key: tmf_... (Admin required) ``` **Request:** ```json { "username": "newuser", "password": "securepass", "display_name": "New User", "email": "user@example.com", "roles": ["MeasurementTec"], "is_admin": false, "language_pref": "it", "theme_pref": "light" } ``` **Response:** HTTP 201 with `UserResponse` **Errors:** - 403: Not admin - 409: Username already exists - 422: Invalid role (must be one of: Maker, MeasurementTec, Metrologist) --- #### PUT `/users/{user_id}` Update a user. **Headers:** ``` X-API-Key: tmf_... (Admin required) ``` **Request:** (all fields optional) ```json { "display_name": "Updated Name", "email": "newemail@example.com", "roles": ["Maker", "MeasurementTec"], "is_admin": true, "language_pref": "en" } ``` **Response:** HTTP 200 with updated `UserResponse` **Errors:** - 403: Not admin - 404: User not found - 422: Invalid role --- #### DELETE `/users/{user_id}` Deactivate a user (soft delete). Sets `active=False` and clears `api_key`. **Headers:** ``` X-API-Key: tmf_... (Admin required) ``` **Response:** HTTP 204 No Content **Errors:** - 400: Cannot deactivate yourself - 403: Not admin - 404: User not found --- #### POST `/users/{user_id}/regenerate-key` Regenerate API key for a user. **Headers:** ``` X-API-Key: tmf_... (Admin required) ``` **Response:** HTTP 200 ```json { "api_key": "tmf_newsecretkey...", "message": "API key regenerated for user username" } ``` **Errors:** - 403: Not admin - 404: User not found --- ### Recipes #### POST `/recipes` Create a new recipe with its initial version (v1). **Headers:** ``` X-API-Key: tmf_... (Maker required) ``` **Request:** ```json { "code": "RECIPE_001", "name": "Measurement Recipe 1", "description": "Optional description", "change_notes": "Initial version" } ``` **Response:** HTTP 201 with `RecipeResponse` (includes `current_version`) **Errors:** - 403: Maker role required - 409: Recipe code already exists --- #### GET `/recipes` List active recipes with pagination and optional search. **Headers:** ``` X-API-Key: tmf_... ``` **Query Parameters:** - `page` (int, default=1, >=1) - `per_page` (int, default=20, 1-100) - `search` (string, optional, max_length=200) **Response:** HTTP 200 with `RecipeListResponse` ```json { "items": [...], "total": 45, "page": 1, "per_page": 20, "pages": 3 } ``` --- #### GET `/recipes/{recipe_id}` Get recipe detail with current version and all tasks/subtasks. **Headers:** ``` X-API-Key: tmf_... ``` **Response:** HTTP 200 with `RecipeResponse` **Errors:** - 404: Recipe not found --- #### GET `/recipes/code/{code}` Look up a recipe by barcode/code. **Requires MeasurementTec role.** **Headers:** ``` X-API-Key: tmf_... (MeasurementTec required) ``` **Response:** HTTP 200 with `RecipeResponse` **Errors:** - 403: MeasurementTec role required - 404: Recipe not found --- #### PUT `/recipes/{recipe_id}` Update a recipe. Creates a new version via **copy-on-write**. Existing measurements remain linked to the original version. **Headers:** ``` X-API-Key: tmf_... (Maker required) ``` **Request:** ```json { "name": "Updated Name", "description": "Updated description", "change_notes": "Updated tolerances" } ``` **Response:** HTTP 200 with `RecipeResponse` (with new version) **Errors:** - 403: Maker role required - 404: Recipe not found - 409: Recipe is inactive --- #### DELETE `/recipes/{recipe_id}` Deactivate a recipe (soft delete). **Headers:** ``` X-API-Key: tmf_... (Maker required) ``` **Response:** HTTP 200 with `RecipeResponse` **Errors:** - 403: Maker role required - 404: Recipe not found --- #### GET `/recipes/{recipe_id}/versions` List all versions of a recipe. **Headers:** ``` X-API-Key: tmf_... ``` **Response:** HTTP 200 with `list[RecipeVersionResponse]` --- #### GET `/recipes/{recipe_id}/versions/{version_number}` Get a specific version with its tasks. **Headers:** ``` X-API-Key: tmf_... ``` **Response:** HTTP 200 with `RecipeVersionResponse` **Errors:** - 404: Version not found --- #### GET `/recipes/{recipe_id}/versions/{version_number}/measurement-count` Count measurements on a specific version. Useful to warn before creating a new version. **Headers:** ``` X-API-Key: tmf_... (Maker required) ``` **Response:** HTTP 200 ```json { "recipe_id": 1, "version_number": 1, "measurement_count": 42 } ``` **Errors:** - 403: Maker role required - 404: Recipe or version not found --- ### Tasks & Subtasks #### GET `/recipes/{recipe_id}/tasks` List all tasks for the current version of a recipe. **Headers:** ``` X-API-Key: tmf_... ``` **Response:** HTTP 200 with `list[TaskResponse]` (sorted by order_index) --- #### POST `/recipes/{recipe_id}/tasks` Add a task to the current version. Creates a **new version via copy-on-write**. **Headers:** ``` X-API-Key: tmf_... (Maker required) ``` **Request:** ```json { "title": "Task Title", "directive": "Measure diameter at point A", "description": "Detailed instructions", "file_type": "image", "annotations_json": null, "subtasks": [ { "marker_number": 1, "description": "Point A diameter", "measurement_type": "length", "nominal": 10.0, "utl": 10.5, "uwl": 10.2, "lwl": 9.8, "ltl": 9.5, "unit": "mm" } ] } ``` **Response:** HTTP 201 with `TaskResponse` **Errors:** - 403: Maker role required - 404: Recipe not found - 409: Recipe is inactive --- #### PUT `/tasks/{task_id}` Update a task. Must belong to current version. **Headers:** ``` X-API-Key: tmf_... (Maker required) ``` **Request:** (all fields optional) ```json { "title": "Updated Title", "directive": "Updated directive" } ``` **Response:** HTTP 200 with `TaskResponse` **Errors:** - 403: Maker role required - 404: Task not found - 409: Task belongs to non-current version --- #### DELETE `/tasks/{task_id}` Delete a task and its subtasks. Must belong to current version. **Headers:** ``` X-API-Key: tmf_... (Maker required) ``` **Response:** HTTP 204 No Content **Errors:** - 403: Maker role required - 404: Task not found - 409: Task belongs to non-current version --- #### PUT `/tasks/reorder` Reorder tasks by providing ordered task IDs. All tasks must belong to the same current version. **Headers:** ``` X-API-Key: tmf_... (Maker required) ``` **Request:** ```json { "task_ids": [3, 1, 2] } ``` **Response:** HTTP 200 with `list[TaskResponse]` (in new order) **Errors:** - 403: Maker role required - 400: task_ids is empty or tasks from different versions - 404: One or more task IDs not found - 409: Tasks belong to non-current version --- #### POST `/tasks/{task_id}/subtasks` Add a subtask to a task. Task must belong to current version. **Headers:** ``` X-API-Key: tmf_... (Maker required) ``` **Request:** ```json { "marker_number": 2, "description": "Additional measurement point", "measurement_type": "length", "nominal": 15.0, "utl": 15.3, "uwl": 15.1, "lwl": 14.9, "ltl": 14.7, "unit": "mm" } ``` **Response:** HTTP 201 with `SubtaskResponse` **Errors:** - 403: Maker role required - 404: Task not found - 409: Task belongs to non-current version OR marker_number already exists --- #### PUT `/subtasks/{subtask_id}` Update a subtask. Its parent task must belong to current version. **Headers:** ``` X-API-Key: tmf_... (Maker required) ``` **Request:** (all fields optional) ```json { "description": "Updated description", "nominal": 15.5 } ``` **Response:** HTTP 200 with `SubtaskResponse` **Errors:** - 403: Maker role required - 404: Subtask not found - 409: Parent task belongs to non-current version --- #### DELETE `/subtasks/{subtask_id}` Delete a subtask. Its parent task must belong to current version. **Headers:** ``` X-API-Key: tmf_... (Maker required) ``` **Response:** HTTP 204 No Content **Errors:** - 403: Maker role required - 404: Subtask not found - 409: Parent task belongs to non-current version --- ### Measurements #### POST `/measurements/` Create a single measurement with auto-calculated pass/fail. **Headers:** ``` X-API-Key: tmf_... (MeasurementTec required) ``` **Request:** ```json { "subtask_id": 1, "version_id": 1, "value": 10.2, "lot_number": "LOT123", "serial_number": "SN456", "input_method": "usb_caliper" } ``` **Response:** HTTP 200 with `MeasurementResponse` ```json { "id": 1, "subtask_id": 1, "version_id": 1, "measured_by": 2, "value": 10.2, "pass_fail": "pass", "deviation": 0.2, "lot_number": "LOT123", "serial_number": "SN456", "input_method": "usb_caliper", "measured_at": "2025-02-07T12:34:56", "synced_to_csv": false } ``` **Pass/Fail Calculation:** - `pass`: value is within LWL and UWL - `warning`: value is within LTL-LWL or UWL-UTL - `fail`: value is outside LTL or UTL **Errors:** - 400: Invalid subtask/version or value out of logical bounds - 403: MeasurementTec role required --- #### POST `/measurements/batch` Create multiple measurements in a batch. **Headers:** ``` X-API-Key: tmf_... (MeasurementTec required) ``` **Request:** ```json { "measurements": [ { "subtask_id": 1, "version_id": 1, "value": 10.2, "lot_number": "LOT123", "serial_number": "SN456", "input_method": "usb_caliper" }, { "subtask_id": 2, "version_id": 1, "value": 15.1, "lot_number": "LOT123", "serial_number": "SN456", "input_method": "manual" } ] } ``` **Response:** HTTP 200 with `list[MeasurementResponse]` **Errors:** - 400: Any measurement validation fails (entire batch fails) - 403: MeasurementTec role required --- #### GET `/measurements/` Query measurements with filters and pagination. **Headers:** ``` X-API-Key: tmf_... (Metrologist required) ``` **Query Parameters:** - `recipe_id` (int, optional) - `version_id` (int, optional) - `subtask_id` (int, optional) - `measured_by` (int, optional) - `lot_number` (string, optional) - `serial_number` (string, optional) - `date_from` (datetime ISO8601, optional) - `date_to` (datetime ISO8601, optional) - `pass_fail` (string, optional, enum: pass|warning|fail) - `page` (int, default=1, >=1) - `per_page` (int, default=50, 1-500) **Response:** HTTP 200 with `MeasurementListResponse` ```json { "items": [...], "total": 256, "page": 1, "per_page": 50, "pages": 6 } ``` **Errors:** - 403: Metrologist role required --- #### GET `/measurements/export/csv` Export measurements to CSV with configurable delimiter and decimal separator. **Headers:** ``` X-API-Key: tmf_... (MeasurementTec or Metrologist required) ``` **Query Parameters:** (same as GET `/measurements/`) **Response:** HTTP 200 with CSV file (streaming) **CSV Format:** ``` id,subtask_id,version_id,measured_by,value,pass_fail,deviation,lot_number,serial_number,input_method,measured_at,synced_to_csv ``` Delimiter and decimal separator read from system settings (`csv_delimiter`, `csv_decimal_separator`). **Errors:** - 403: Insufficient role --- ### Files #### POST `/files/upload` Upload an image or PDF file. **Requires Maker role.** **Headers:** ``` X-API-Key: tmf_... (Maker required) Content-Type: multipart/form-data ``` **Form Parameters:** - `file` (UploadFile, required): Image (JPEG, PNG, GIF, WebP) or PDF - `recipe_id` (int, optional): Target recipe - `version_id` (int, optional): Target version **Constraints:** - Max file size: 50 MB (configurable via `MAX_UPLOAD_SIZE_MB`) - Allowed types: JPEG, PNG, GIF, WebP, PDF - Max filename length: 255 characters **Response:** HTTP 200 ```json { "file_path": "1/1/image.jpg", "thumbnail_path": "1/1/thumbnails/image.jpg", "file_type": "image/jpeg", "file_size": 204800 } ``` **Notes:** - Files stored in `uploads/{recipe_id}/{version_id}/` or `uploads/general/` - Filenames sanitized (alphanumeric, dots, hyphens, underscores only) - Thumbnails auto-generated for images (200x200px) - Duplicate filenames auto-numbered **Errors:** - 400: Invalid file type, file size exceeded, invalid filename - 403: Maker role required --- #### GET `/files/{file_path}` Serve a file from the uploads directory. **Headers:** ``` X-API-Key: tmf_... ``` **Path Parameter:** - `file_path` (string): Relative path from `uploads/` directory **Example:** ``` GET /files/1/1/image.jpg GET /files/1/1/thumbnails/image.jpg ``` **Response:** HTTP 200 with file content (Content-Type inferred) **Errors:** - 403: Path traversal attempt - 404: File not found --- #### DELETE `/files/{file_path}` Delete a file from the uploads directory. **Requires Maker role.** **Headers:** ``` X-API-Key: tmf_... (Maker required) ``` **Notes:** - Associated thumbnail also deleted if it exists **Response:** HTTP 204 No Content **Errors:** - 403: Maker role required or path traversal attempt - 404: File not found --- ### Settings #### GET `/settings/` Get all system settings as a dictionary. **Headers:** ``` X-API-Key: tmf_... ``` **Response:** HTTP 200 ```json { "company_logo_path": "logos/company_logo.png", "csv_delimiter": ",", "csv_decimal_separator": "." } ``` --- #### PUT `/settings/` Update multiple system settings. **Requires Admin role.** **Headers:** ``` X-API-Key: tmf_... (Admin required) ``` **Request:** ```json { "csv_delimiter": ";", "csv_decimal_separator": ",", "custom_setting": "value" } ``` **Response:** HTTP 200 ```json { "message": "Updated 3 settings", "updated_keys": ["csv_delimiter", "csv_decimal_separator", "custom_setting"] } ``` **Errors:** - 403: Admin role required --- #### POST `/settings/logo` Upload company logo. **Requires Admin role.** **Headers:** ``` X-API-Key: tmf_... (Admin required) Content-Type: multipart/form-data ``` **Form Parameters:** - `file` (UploadFile, required): Image file (JPEG, PNG, GIF, WebP, SVG) **Response:** HTTP 200 ```json { "message": "Company logo uploaded successfully", "logo_path": "logos/company_logo.png", "file_size": 102400 } ``` **Notes:** - Stored as `uploads/logos/company_logo.{ext}` - Updates `company_logo_path` setting in database **Errors:** - 400: Invalid file type or size exceeded - 403: Admin role required --- ### Statistics All statistics endpoints **require Metrologist role**. #### GET `/statistics/summary` Get pass/fail/warning summary for filtered measurements. **Headers:** ``` X-API-Key: tmf_... (Metrologist required) ``` **Query Parameters:** - `recipe_id` (int, required) - `version_id` (int, optional) - `subtask_id` (int, optional) - `date_from` (datetime ISO8601, optional) - `date_to` (datetime ISO8601, optional) - `operator_id` (int, optional) - `lot_number` (string, optional) - `serial_number` (string, optional) **Response:** HTTP 200 with `SummaryData` ```json { "total": 100, "pass_count": 85, "warning_count": 10, "fail_count": 5, "pass_percentage": 85.0, "warning_percentage": 10.0, "fail_percentage": 5.0 } ``` --- #### GET `/statistics/capability` Get capability indices (Cp/Cpk/Pp/Ppk) for a specific subtask. **Headers:** ``` X-API-Key: tmf_... (Metrologist required) ``` **Query Parameters:** - `recipe_id` (int, required) - `subtask_id` (int, required) - `version_id` (int, optional) - `date_from` (datetime ISO8601, optional) - `date_to` (datetime ISO8601, optional) - `operator_id` (int, optional) - `lot_number` (string, optional) - `serial_number` (string, optional) **Response:** HTTP 200 with `CapabilityData` ```json { "n": 50, "mean": 10.05, "stdev": 0.15, "cp": 1.67, "cpk": 1.50, "pp": 1.60, "ppk": 1.40 } ``` --- #### GET `/statistics/control-chart` Get control chart data with UCL/LCL and out-of-control detection. **Headers:** ``` X-API-Key: tmf_... (Metrologist required) ``` **Query Parameters:** (same as capability) **Response:** HTTP 200 with `ControlChartData` ```json { "points": [ { "x": 0, "y": 10.1, "timestamp": "2025-02-01T08:00:00" }, ... ], "ucl": 10.5, "lcl": 9.5, "center_line": 10.0, "out_of_control_indices": [5, 12, 18] } ``` --- #### GET `/statistics/histogram` Get histogram data with normal curve overlay. **Headers:** ``` X-API-Key: tmf_... (Metrologist required) ``` **Query Parameters:** - `recipe_id` (int, required) - `subtask_id` (int, required) - `version_id` (int, optional) - `date_from` (datetime ISO8601, optional) - `date_to` (datetime ISO8601, optional) - `operator_id` (int, optional) - `lot_number` (string, optional) - `serial_number` (string, optional) - `n_bins` (int, default=20, 5-100) **Response:** HTTP 200 with `HistogramData` ```json { "bins": [9.0, 9.2, 9.4, 9.6, ...], "frequencies": [0, 2, 5, 12, ...], "normal_curve_y": [0.001, 0.003, 0.008, ...] } ``` --- #### GET `/statistics/subtasks` Get subtasks for a recipe (for filter dropdown). **Headers:** ``` X-API-Key: tmf_... (Metrologist required) ``` **Query Parameters:** - `recipe_id` (int, required) - `version_id` (int, optional): Use current version if not provided **Response:** HTTP 200 with list of subtask objects ```json [ { "id": 1, "marker_number": 1, "description": "Diameter at point A", "task_title": "Task 1: Measure", "nominal": 10.0, "utl": 10.5, "ltl": 9.5 }, ... ] ``` --- ### Reports All report endpoints **require Metrologist role**. #### GET `/reports/spc` Generate and download SPC PDF report with charts and statistics. **Headers:** ``` X-API-Key: tmf_... (Metrologist required) ``` **Query Parameters:** - `recipe_id` (int, required) - `subtask_id` (int, required) - `version_id` (int, optional) - `date_from` (datetime ISO8601, optional) - `date_to` (datetime ISO8601, optional) - `operator_id` (int, optional) - `lot_number` (string, optional) - `serial_number` (string, optional) **Response:** HTTP 200 with PDF file (streaming) **Content-Type:** `application/pdf` **Filename:** `spc_report_recipe{recipe_id}_st{subtask_id}.pdf` **Errors:** - 403: Metrologist role required - 500: Report generation failed --- #### GET `/reports/measurements` Generate and download measurement table PDF report. **Headers:** ``` X-API-Key: tmf_... (Metrologist required) ``` **Query Parameters:** - `recipe_id` (int, required) - `subtask_id` (int, optional) - `version_id` (int, optional) - `date_from` (datetime ISO8601, optional) - `date_to` (datetime ISO8601, optional) - `operator_id` (int, optional) - `lot_number` (string, optional) - `serial_number` (string, optional) **Response:** HTTP 200 with PDF file (streaming) **Content-Type:** `application/pdf` **Filename:** `measurements_report_recipe{recipe_id}.pdf` **Errors:** - 403: Metrologist role required - 500: Report generation failed --- ## Pagination List endpoints return paginated responses with the following format: ```json { "items": [...], "total": 150, "page": 1, "per_page": 20, "pages": 8 } ``` **Parameters:** - `page`: Current page number (1-indexed, default=1) - `per_page`: Items per page (default varies by endpoint, max varies) **Example:** ```bash GET /api/recipes?page=2&per_page=50 ``` --- ## Filtering Most list endpoints support filtering via query parameters. Common filters: | Parameter | Type | Example | |-----------|------|---------| | `search` | string | `/recipes?search=recipe%20name` | | `date_from` | ISO8601 datetime | `/measurements?date_from=2025-01-01T00:00:00` | | `date_to` | ISO8601 datetime | `/measurements?date_to=2025-02-07T23:59:59` | | `lot_number` | string | `/measurements?lot_number=LOT123` | | `serial_number` | string | `/measurements?serial_number=SN456` | | `pass_fail` | string (pass\|warning\|fail) | `/measurements?pass_fail=fail` | --- ## Response Schemas ### UserResponse ```json { "id": 1, "username": "operator1", "display_name": "Operator One", "email": "op1@example.com", "roles": ["MeasurementTec"], "is_admin": false, "active": true, "language_pref": "it", "theme_pref": "light" } ``` ### RecipeResponse ```json { "id": 1, "code": "RECIPE_001", "name": "Recipe Name", "description": "Description", "active": true, "created_at": "2025-01-01T10:00:00", "updated_at": "2025-01-15T14:30:00", "created_by": 1, "current_version": { ... } } ``` ### TaskResponse ```json { "id": 1, "version_id": 1, "order_index": 0, "title": "Task Title", "directive": "Measurement directive", "description": "Detailed description", "file_type": "image", "annotations_json": null, "subtasks": [...] } ``` ### SubtaskResponse ```json { "id": 1, "task_id": 1, "marker_number": 1, "description": "Measurement point description", "measurement_type": "length", "nominal": 10.0, "utl": 10.5, "uwl": 10.2, "lwl": 9.8, "ltl": 9.5, "unit": "mm" } ``` ### MeasurementResponse ```json { "id": 1, "subtask_id": 1, "version_id": 1, "measured_by": 2, "value": 10.15, "pass_fail": "pass", "deviation": 0.15, "lot_number": "LOT123", "serial_number": "SN456", "input_method": "usb_caliper", "measured_at": "2025-02-07T12:34:56", "synced_to_csv": false } ```