Stations were the headline V2.0.0 feature but had no user-facing
documentation outside the architecture page. Filled the gap across
the three operational docs.
USER_GUIDE.md
- New entries in "Key Concepts": Station and Station assignment.
- New "Recipes you see are filtered by station" subsection in the
MeasurementTec workflow, explaining why the Select Recipe page may
legitimately show fewer recipes than expected and what the
"Stazione non configurata" error means at the operator level.
- New "Station Management" section under Admin Workflow covering:
the mental model, station create/edit/delete, the two-column
recipe-assignment modal, the immutable-code rule, the role of the
ST-DEFAULT seed station, and the tablet deployment cheat sheet.
- Admin role description updated to mention stations.
DEPLOYMENT.md
- Environment Variables Reference: added STATION_CODE row and noted
that an empty value triggers the deliberate fail-fast HTTP 503 on
/measure/select. Updated RATE_LIMIT_GENERAL default (300, per the
V2.0.0 perf change). Clarified UPLOAD_DIR resolves against the
project root.
API.md
- New "Stations" endpoint section listing all eight routes with
request/response examples and the 401/403/404/409 error contract:
GET / POST /stations, GET /stations/{id}, PUT /stations/{id},
DELETE /stations/{id}, GET /stations/{id}/recipes,
GET /stations/by-code/{code}/recipes (the operator-facing one used
by the Flask client), POST /stations/{id}/recipes,
DELETE /stations/{id}/recipes/{recipe_id}.
- TOC updated with the new "Stations" anchor.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
30 KiB
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
- Authentication
- Authorization & Roles
- Rate Limiting
- Response Format
- Error Codes
- Endpoints
- Pagination
- Filtering
Authentication
Login Flow
-
POST
/auth/login- Obtain API Key{ "username": "operator1", "password": "password123" }Response:
{ "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" } -
Include API Key in Subsequent Requests
X-API-Key: tmf_abc123def456ghi789jkl -
POST
/auth/logout- Invalidate API Key- Requires
X-API-Keyheader - Returns HTTP 204 No Content on success
- Requires
Example: Complete Auth Flow
# 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):
{
"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:
{
"id": 1,
"username": "operator1",
"display_name": "Operator One",
...
}
Error Response
{
"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:
{
"status": "ok",
"service": "TieMeasureFlow API",
"version": "0.1.0"
}
Auth
POST /auth/login
Login with username and password to receive an API key.
Request:
{
"username": "string",
"password": "string"
}
Response: HTTP 200 with LoginResponse
{
"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:
{
"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:
{
"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)
{
"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
{
"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:
{
"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
{
"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:
{
"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
{
"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:
{
"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)
{
"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:
{
"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:
{
"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)
{
"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:
{
"subtask_id": 1,
"version_id": 1,
"value": 10.2,
"lot_number": "LOT123",
"serial_number": "SN456",
"input_method": "usb_caliper"
}
Response: HTTP 200 with MeasurementResponse
{
"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 UWLwarning: value is within LTL-LWL or UWL-UTLfail: 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:
{
"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
{
"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 PDFrecipe_id(int, optional): Target recipeversion_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
{
"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}/oruploads/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 fromuploads/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
{
"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:
{
"csv_delimiter": ";",
"csv_decimal_separator": ",",
"custom_setting": "value"
}
Response: HTTP 200
{
"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
{
"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_pathsetting in database
Errors:
- 400: Invalid file type or size exceeded
- 403: Admin role required
Stations
Stations model the physical measurement posts on the shop floor. Each Flask client identifies itself through STATION_CODE, and the operator only sees recipes assigned to that station. See the user guide section "Admin Workflow → Station Management" for the operational model.
GET /stations
List stations. Admin only.
Query parameters:
active_only(bool, defaultfalse): iftrue, return only stations whereactive = true.
Response 200:
[
{
"id": 1,
"code": "ST-DEFAULT",
"name": "Default Station",
"location": "Initial seed - change me",
"notes": null,
"active": true,
"created_by": 5,
"created_at": "2026-04-26T10:12:06"
}
]
Errors:
- 401: Missing or invalid API key
- 403: Admin role required
POST /stations
Create a station. Admin only.
Request body:
{
"code": "ST-LINEA-A",
"name": "Linea A — Tornitura alberi",
"location": "Reparto 2 — Cella 3",
"notes": "Provisioned 2026-04-26 by Adriano",
"active": true
}
code (1-100 chars) and name (1-255 chars) are required. location is optional (≤ 255 chars). active defaults to true.
Response 201: same shape as GET /stations items.
Errors:
- 400: Invalid payload (missing
code/name, exceeded length) - 403: Admin role required
- 409: Station with that code already exists
GET /stations/{station_id}
Get a single station by id. Admin only.
Response 200: same as the list item shape.
Errors:
- 403: Admin role required
- 404: Station not found
PUT /stations/{station_id}
Update a station's editable fields. Admin only.
Request body (all fields optional):
{
"name": "Linea A — riconfigurata",
"location": "Reparto 2 — Cella 4",
"notes": "Moved cell on 2026-05-12",
"active": false
}
Note: the code field is not in the schema. Codes are immutable on purpose (changing the code would orphan every tablet pointing at it).
Response 200: the updated station.
Errors:
- 400: Invalid payload
- 403: Admin role required
- 404: Station not found
DELETE /stations/{station_id}
Delete a station. Admin only.
The deletion cascades to every row in station_recipe_assignments for that station. Existing measurements are NOT affected (measurements link to recipe versions, not stations).
Response 204: no body.
Errors:
- 403: Admin role required
- 404: Station not found
GET /stations/{station_id}/recipes
Admin view of the recipes currently assigned to a station. Returns the same projection used by the assignment modal in /admin/stations. Admin only.
Response 200:
[
{
"id": 2,
"code": "DEMO-001",
"name": "Demo Measurement Recipe",
"active": true
}
]
Errors:
- 403: Admin role required
- 404: Station not found
GET /stations/by-code/{code}/recipes
Operator view (any authenticated user, no admin requirement). Returns the active recipes assigned to the station whose code matches {code}. The Flask client calls this endpoint at every page load of /measure/select, passing its own STATION_CODE.
Response 200: same shape as GET /stations/{station_id}/recipes.
Errors:
- 401: Missing or invalid API key
- 404: Station not found OR station is not active (operator-facing endpoint deliberately treats both cases as 404 to avoid leaking the existence of disabled stations)
POST /stations/{station_id}/recipes
Assign an existing recipe to a station. Admin only.
Request body:
{ "recipe_id": 2 }
Response 201:
{
"id": 1,
"station_id": 1,
"recipe_id": 2,
"assigned_by": 5,
"assigned_at": "2026-04-26T10:12:06"
}
Errors:
- 403: Admin role required
- 404: Station or recipe not found
- 409: Recipe already assigned to this station
DELETE /stations/{station_id}/recipes/{recipe_id}
Remove an existing assignment. Admin only.
Response 204: no body.
Errors:
- 403: Admin role required
- 404: Station/recipe not found, or no assignment between them
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
{
"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
{
"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
{
"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
{
"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
[
{
"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:
{
"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:
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
{
"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
{
"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
{
"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
{
"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
{
"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
}