openapi: 3.0.3 info: title: ConstruProgress Mobile API version: "1.1.0" description: > Offline-first sync API for the mobile app. Auth via Laravel Sanctum bearer tokens (ability `mobile-sync`). All protected endpoints require `Authorization: Bearer `. See docs/MOBILE_SYNC_PROTOCOL.md. ## Changelog (from v1.0) - Inspection templates are now **global** and assigned to projects via pivot. `bundle.templates` and `/templates` return templates *assigned* to the accessible projects (no longer filtered by `project_id`). `template.phase_id` was **removed**. `project_id` remains in the schema for compatibility but no longer determines visibility. - Template `fields[]` items may include `group` (section), `question` (short prompt shown at inspection time) and `help` (long help text). - New entities in the bundle: `feature_types` (global catalogue), `issue_tasks`, `issue_comments`. - `feature` gains `feature_type_id`, `is_active`; `issue` gains `type`. - New `/sync` ops: `issue.update`, `issue_task.create`, `issue_task.update`, `issue_comment.create`. `feature.update` accepts `is_active` and `feature_type_id`; `issue.create`/`update` accept `type`. - `POST /media` `parent_entity` also accepts `issue_task` and `issue_comment`. - `deleted` tombstones include `issue_tasks` and `issue_comments`. servers: - url: /api/v1 security: - bearerAuth: [] paths: /login: post: summary: Issue a device token security: [] requestBody: required: true content: application/json: schema: type: object required: [email, password, device_name] properties: email: { type: string, format: email } password: { type: string } device_name: { type: string } app_version: { type: string, nullable: true } responses: "200": description: Token issued content: application/json: schema: type: object properties: token: { type: string } user: { $ref: '#/components/schemas/User' } "422": { description: Invalid credentials } /me: get: summary: Current user + effective permissions responses: "200": description: OK content: application/json: schema: type: object properties: user: { $ref: '#/components/schemas/User' } "401": { description: Unauthenticated } /logout: post: summary: Revoke the current device token responses: "200": { description: Logged out } /projects: get: summary: Projects the user can access responses: "200": { description: OK } /projects/{project}/bundle: get: summary: Offline bundle (full, or delta when `since` is given) parameters: - name: project in: path required: true schema: { type: integer } - name: since in: query required: false description: > ISO8601 timestamp. Returns only records changed after it, plus `deleted` tombstones. MUST be URL-encoded (the `+` offset). schema: { type: string, format: date-time } responses: "200": description: Bundle content: application/json: schema: { $ref: '#/components/schemas/Bundle' } "403": { description: Not a member of the project } /templates: get: summary: Global inspection templates ASSIGNED to any accessible project (via pivot) description: > Templates are a global catalogue. This endpoint returns the templates that have been *assigned* (via `inspection_template_project` pivot) to any of the user's accessible projects. Each item includes `version` (updated_at timestamp) and `hash` for change detection. parameters: - name: since in: query required: false schema: { type: string, format: date-time } responses: "200": { description: OK } /sync: post: summary: Push a batch of offline mutations (idempotent by uuid) requestBody: required: true content: application/json: schema: type: object required: [operations] properties: operations: type: array items: { $ref: '#/components/schemas/Operation' } responses: "200": description: Per-operation results content: application/json: schema: type: object properties: results: type: array items: { $ref: '#/components/schemas/OperationResult' } /media: post: summary: Upload a file (multipart) and attach it to a parent record requestBody: required: true content: multipart/form-data: schema: type: object required: [uuid, parent_entity, parent_id, file] properties: uuid: { type: string, format: uuid } parent_entity: { type: string, enum: [feature, issue, issue_task, issue_comment, project, phase, layer] } parent_id: { type: integer } file: { type: string, format: binary } category: { type: string, enum: [image, document, other] } description: { type: string } responses: "200": { description: applied | duplicate } "403": { description: Forbidden } components: securitySchemes: bearerAuth: type: http scheme: bearer schemas: User: type: object properties: id: { type: integer } name: { type: string } email: { type: string } roles: { type: array, items: { type: string } } permissions: { type: array, items: { type: string } } Operation: type: object required: [entity, op, uuid, data] properties: entity: { type: string, enum: [progress_update, inspection, issue, issue_task, issue_comment, feature] } op: { type: string, enum: [create, update] } uuid: { type: string, format: uuid, description: client-generated idempotency key } client_updated_at: { type: string, format: date-time } data: { type: object } example: entity: feature op: update uuid: 0f8e...-uuid client_updated_at: "2026-06-18T12:00:00+00:00" data: { id: 5, status: completed, progress: 100 } OperationResult: type: object properties: uuid: { type: string, format: uuid } status: { type: string, enum: [applied, duplicate, conflict, error] } server_id: { type: integer, nullable: true } error: { type: string, nullable: true } server: { type: object, nullable: true, description: current server value on conflict } Bundle: type: object properties: server_time: { type: string, format: date-time } project: { type: object } phases: { type: array, items: { type: object } } layers: { type: array, items: { type: object } } features: { type: array, items: { type: object } } feature_types: type: array description: Global catalogue of feature types (id, name, color) items: { type: object } inspections: { type: array, items: { type: object } } issues: type: array description: > Includes `type` (defect|safety|quality|documentation|other), `status` (open|in_review|resolved|closed), `priority` (low|medium|high|critical). items: { type: object } issue_tasks: type: array description: Checklist tasks per issue. items: { type: object } issue_comments: type: array description: Comment thread per issue. items: { type: object } templates: type: array description: > Templates assigned to this project via pivot (no longer filtered by project_id). Each item exposes version/hash for change detection and `fields[]` where each field may include `group`, `question`, `help`. items: { type: object } media: { type: array, items: { type: object } } deleted: type: object description: tombstones (ids of soft-deleted records) when `since` is given properties: phases: { type: array, items: { type: integer } } layers: { type: array, items: { type: integer } } features: { type: array, items: { type: integer } } inspections: { type: array, items: { type: integer } } issues: { type: array, items: { type: integer } } issue_tasks: { type: array, items: { type: integer } } issue_comments: { type: array, items: { type: integer } }