openapi: 3.0.0 info: title: 'Casadoc Mobile API (plain PHP)' description: 'Mobile REST API in plain PHP. Bearer tokens, uniform JSON.' version: 1.0.0 servers: - url: /userportal/api description: 'Casadoc Mobile API' paths: /account-delete.php: post: tags: - Auth summary: 'Delete the account and all related data' description: 'Immediate hard delete: documents on disk, homes, owners, sharing, tokens and the user row. Irreversible.' requestBody: required: true content: application/json: schema: required: - confirm properties: confirm: description: 'guard against an accidental call' type: boolean example: true type: object responses: '200': $ref: '#/components/responses/Success' '401': $ref: '#/components/responses/Unauthorized' '422': $ref: '#/components/responses/ValidationError' security: - bearerAuth: [] /document-file-delete.php: post: tags: - Documents summary: 'Delete an uploaded file' requestBody: required: true content: application/json: schema: required: - id properties: id: description: doc_storage.id type: integer type: object responses: '200': $ref: '#/components/responses/Success' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' security: - bearerAuth: [] /document-file-replace.php: post: tags: - Documents summary: 'Replace the file of an existing document' description: 'Atomic swap: the new file is written first, the row is repointed, then the old file is removed. Metadata (document_id, expiry, note, created_at) is kept and max_documents is not checked.' requestBody: required: true content: multipart/form-data: schema: required: - id - file properties: id: description: doc_storage.id type: integer file: description: 'PDF or image' type: string format: binary title: description: 'optional new display name' type: string type: object responses: '200': description: Replaced content: application/json: schema: properties: data: { $ref: '#/components/schemas/UploadedFile' } type: object '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '422': $ref: '#/components/responses/ValidationError' security: - bearerAuth: [] /document-file-update.php: post: tags: - Documents summary: "Update an uploaded file's metadata" description: 'Partial update: only the fields present in the body are changed.' requestBody: required: true content: application/json: schema: required: - id properties: id: description: doc_storage.id type: integer expiry_date: type: string format: date nullable: true note: type: string nullable: true title: description: 'display name; null resets it to the requirement name' type: string nullable: true document_id: description: 'move file to another requirement' type: integer type: object responses: '200': description: Updated content: application/json: schema: properties: data: { $ref: '#/components/schemas/UploadedFile' } type: object '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '422': $ref: '#/components/responses/ValidationError' security: - bearerAuth: [] /document-file.php: get: tags: - Documents summary: 'Download an uploaded file' description: 'Access: home/owner owner or accepted share.' parameters: - name: id in: query description: doc_storage.id required: true schema: type: integer responses: '200': description: 'Binary file' content: application/octet-stream: schema: type: string format: binary '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' security: - bearerAuth: [] /document-templates.php: get: tags: - Reference summary: 'Document templates reference' parameters: - name: slug in: query description: 'filter by pages.slug' schema: type: string responses: '200': description: OK content: application/json: schema: properties: data: { type: array, items: { $ref: '#/components/schemas/DocumentTemplate' } } type: object '401': $ref: '#/components/responses/Unauthorized' security: - bearerAuth: [] /document-upload.php: post: tags: - Documents summary: 'Upload a document file for a home' requestBody: required: true content: multipart/form-data: schema: required: - idhome - document_id - file properties: idhome: type: integer document_id: type: integer file: description: 'PDF or image' type: string format: binary expiry_date: type: string format: date note: type: string title: description: 'optional display name' type: string type: object responses: '201': description: 'File uploaded' content: application/json: schema: properties: data: { $ref: '#/components/schemas/UploadedFile' } type: object '403': $ref: '#/components/responses/Forbidden' '422': $ref: '#/components/responses/ValidationError' security: - bearerAuth: [] /documents-download.php: get: tags: - Documents summary: 'ZIP of uploaded home documents' description: 'Owner gets all files; share recipient gets only allowed sections.' parameters: - name: idhome in: query required: true schema: type: integer responses: '200': description: ZIP content: application/zip: schema: type: string format: binary '403': $ref: '#/components/responses/Forbidden' security: - bearerAuth: [] /documents.php: get: tags: - Documents summary: 'Home document requirements with uploaded files' parameters: - name: idhome in: query required: true schema: type: integer - name: slug in: query schema: type: string default: legal - name: section_id in: query schema: type: integer - name: only_required in: query schema: type: boolean responses: '200': description: OK content: application/json: schema: properties: home: { $ref: '#/components/schemas/Home' } data: { type: array, items: { $ref: '#/components/schemas/DocumentRequirement' } } type: object '403': $ref: '#/components/responses/Forbidden' security: - bearerAuth: [] /email-verify-resend.php: post: tags: - Auth summary: 'Re-send the e-mail verification code' description: 'Always answers 200 so the endpoint cannot be used to probe which e-mails are registered. Silently ignored more often than once per 60 seconds.' requestBody: required: true content: application/json: schema: required: - email properties: email: type: string format: email type: object responses: '200': $ref: '#/components/responses/Success' security: [] /email-verify.php: post: tags: - Auth summary: 'Confirm the e-mail with a 6-digit code and log in' description: 'On success a bearer token is issued, so the app enters without a second login. Error codes: code_invalid (retry) or code_expired (request a new code).' requestBody: required: true content: application/json: schema: required: - email - code - device_name properties: email: type: string format: email code: type: string example: '123456' device_name: type: string example: 'iPhone 15' type: object responses: '200': description: Verified content: application/json: schema: properties: data: { properties: { token: { type: string }, user: { $ref: '#/components/schemas/User' } }, type: object } type: object '422': $ref: '#/components/responses/ValidationError' security: [] /home-delete.php: post: tags: - Homes summary: 'Delete property (cascade files and sharing)' requestBody: required: true content: application/json: schema: required: - idhome properties: idhome: type: integer type: object responses: '200': $ref: '#/components/responses/Success' '403': $ref: '#/components/responses/Forbidden' security: - bearerAuth: [] /home-owner-attach.php: post: tags: - Owners summary: 'Attach owner to home with share' description: 'Requires ownership of both home and owner. Ensures total share ≤ 100%.' requestBody: required: true content: application/json: schema: required: - idhome - owner_id - ownership_percentage properties: idhome: type: integer owner_id: type: integer ownership_percentage: type: number format: float notes: type: string type: object responses: '200': $ref: '#/components/responses/Success' '403': $ref: '#/components/responses/Forbidden' '422': $ref: '#/components/responses/ValidationError' security: - bearerAuth: [] /home-owner-detach.php: post: tags: - Owners summary: 'Detach owner from home' requestBody: required: true content: application/json: schema: required: - idhome - owner_id properties: idhome: type: integer owner_id: type: integer type: object responses: '200': $ref: '#/components/responses/Success' '403': $ref: '#/components/responses/Forbidden' security: - bearerAuth: [] /home-owners.php: get: tags: - Owners summary: 'Home owners with shares' parameters: - name: idhome in: query required: true schema: type: integer responses: '200': description: OK content: application/json: schema: properties: data: { type: array, items: { $ref: '#/components/schemas/HomeOwner' } } type: object '403': $ref: '#/components/responses/Forbidden' security: - bearerAuth: [] /home-photo.php: post: tags: - Homes summary: 'Upload property main photo' requestBody: required: true content: multipart/form-data: schema: required: - idhome - photo properties: idhome: type: integer photo: description: image type: string format: binary type: object responses: '200': description: OK content: application/json: schema: properties: data: { $ref: '#/components/schemas/Home' } type: object '403': $ref: '#/components/responses/Forbidden' '422': $ref: '#/components/responses/ValidationError' security: - bearerAuth: [] /home-report.php: get: tags: - Homes summary: 'Property PDF report' description: 'Access: owner or accepted share. Requires TCPDF library (composer).' parameters: - name: idhome in: query required: true schema: type: integer responses: '200': description: PDF content: application/pdf: schema: type: string format: binary '403': $ref: '#/components/responses/Forbidden' security: - bearerAuth: [] /home-save.php: post: tags: - Homes summary: 'Create or update property' description: 'idhome missing/0 creates; otherwise updates own property.' requestBody: required: true content: application/json: schema: properties: idhome: type: integer name: type: string comment: type: string fulladdress: type: string address: type: string zip: type: string city: type: string country: type: string latitude: type: string longitude: type: string cadastral_municipality: type: string cadastral_section: type: string cadastral_sheet: type: string cadastral_particle: type: string cadastral_sub: type: string cadastral_category: type: string cadastral_class: type: string cadastral_surface: type: string cadastral_rendita: type: string cadastral_notes: type: string type: object responses: '200': description: OK content: application/json: schema: properties: data: { $ref: '#/components/schemas/Home' } type: object '403': $ref: '#/components/responses/Forbidden' '422': $ref: '#/components/responses/ValidationError' security: - bearerAuth: [] /home.php: get: tags: - Homes summary: 'Property data' parameters: - name: idhome in: query required: true schema: type: integer responses: '200': description: OK content: application/json: schema: properties: data: { $ref: '#/components/schemas/Home' } type: object '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' security: - bearerAuth: [] /homes-shared.php: get: tags: - Sharing summary: 'Homes shared with me (status=accepted)' responses: '200': description: OK content: application/json: schema: properties: data: { type: array, items: { $ref: '#/components/schemas/Home' } } type: object '401': $ref: '#/components/responses/Unauthorized' security: - bearerAuth: [] /homes.php: get: tags: - Homes summary: 'My properties' responses: '200': description: OK content: application/json: schema: properties: data: { type: array, items: { $ref: '#/components/schemas/Home' } } type: object '401': $ref: '#/components/responses/Unauthorized' security: - bearerAuth: [] /login.php: post: tags: - Auth summary: 'Log in, issue bearer token' requestBody: required: true content: application/json: schema: required: - login - password - device_name properties: login: description: 'e-mail or username' type: string password: type: string format: password device_name: type: string example: 'iPhone 15' type: object responses: '200': description: Success content: application/json: schema: properties: data: { properties: { token: { type: string }, user: { $ref: '#/components/schemas/User' } }, type: object } type: object '401': $ref: '#/components/responses/Unauthorized' '403': description: 'E-mail not verified (code email_not_verified)' content: application/json: schema: $ref: '#/components/schemas/Error' '422': $ref: '#/components/responses/ValidationError' security: [] /logout.php: post: tags: - Auth summary: 'Revoke current token' responses: '200': $ref: '#/components/responses/Success' '401': $ref: '#/components/responses/Unauthorized' security: - bearerAuth: [] /me-avatar.php: post: tags: - Auth summary: "Upload the current user's avatar" description: 'Stored under public/upload/users; the User.avatar field is returned as an absolute URL.' requestBody: required: true content: multipart/form-data: schema: required: - avatar properties: avatar: description: image type: string format: binary type: object responses: '200': description: OK content: application/json: schema: properties: data: { $ref: '#/components/schemas/User' } type: object '401': $ref: '#/components/responses/Unauthorized' '422': $ref: '#/components/responses/ValidationError' security: - bearerAuth: [] /me-save.php: post: tags: - Auth summary: "Update the current user's profile" description: 'Partial update: only the keys present in the body are changed. E-mail is read-only here.' requestBody: required: true content: application/json: schema: properties: first_name: type: string nullable: true last_name: type: string nullable: true phone: type: string nullable: true address: type: string nullable: true type: object responses: '200': description: OK content: application/json: schema: properties: data: { $ref: '#/components/schemas/User' } type: object '401': $ref: '#/components/responses/Unauthorized' '422': $ref: '#/components/responses/ValidationError' security: - bearerAuth: [] /me.php: get: tags: - Auth summary: 'Current user' responses: '200': description: OK content: application/json: schema: properties: data: { $ref: '#/components/schemas/User' } type: object '401': $ref: '#/components/responses/Unauthorized' security: - bearerAuth: [] /owner-delete.php: post: tags: - Owners summary: 'Delete owner' requestBody: required: true content: application/json: schema: required: - owner_id properties: owner_id: type: integer type: object responses: '200': $ref: '#/components/responses/Success' '403': $ref: '#/components/responses/Forbidden' security: - bearerAuth: [] /owner-document-upload.php: post: tags: - Documents summary: 'Upload a document file for an owner' requestBody: required: true content: multipart/form-data: schema: required: - owner_id - document_id - file properties: owner_id: type: integer document_id: type: integer file: description: 'PDF or image' type: string format: binary expiry_date: type: string format: date note: type: string title: description: 'optional display name' type: string type: object responses: '201': description: 'File uploaded' content: application/json: schema: properties: data: { $ref: '#/components/schemas/UploadedFile' } type: object '403': $ref: '#/components/responses/Forbidden' '422': $ref: '#/components/responses/ValidationError' security: - bearerAuth: [] /owner-documents.php: get: tags: - Documents summary: 'Owner personal documents with uploaded files' parameters: - name: owner_id in: query required: true schema: type: integer - name: slug in: query schema: type: string responses: '200': description: OK content: application/json: schema: properties: data: { type: array, items: { $ref: '#/components/schemas/DocumentRequirement' } } type: object '403': $ref: '#/components/responses/Forbidden' security: - bearerAuth: [] /owner-save.php: post: tags: - Owners summary: 'Create or update owner' description: 'owner_id missing/0 → create; otherwise update own owner.' requestBody: required: true content: application/json: schema: required: - tax_code properties: owner_id: type: integer owner_type: type: string enum: [individual, company] first_name: type: string last_name: type: string company_name: type: string tax_code: type: string email: type: string phone: type: string address: type: string postal_code: type: string city: type: string province: type: string country: type: integer role: type: string type: object responses: '200': description: OK content: application/json: schema: properties: data: { $ref: '#/components/schemas/Owner' } type: object '403': $ref: '#/components/responses/Forbidden' '422': $ref: '#/components/responses/ValidationError' security: - bearerAuth: [] /owner.php: get: tags: - Owners summary: 'Owner details' parameters: - name: owner_id in: query required: true schema: type: integer responses: '200': description: OK content: application/json: schema: properties: data: { $ref: '#/components/schemas/Owner' } type: object '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' security: - bearerAuth: [] /owners.php: get: tags: - Owners summary: 'My owners' responses: '200': description: OK content: application/json: schema: properties: data: { type: array, items: { $ref: '#/components/schemas/Owner' } } type: object '401': $ref: '#/components/responses/Unauthorized' security: - bearerAuth: [] /pages.php: get: tags: - Reference summary: 'Category pages reference' responses: '200': description: OK content: application/json: schema: properties: data: { type: array, items: { $ref: '#/components/schemas/Page' } } type: object '401': $ref: '#/components/responses/Unauthorized' security: - bearerAuth: [] /password-change.php: post: tags: - Auth summary: 'Change the password of the signed-in user' description: 'Revokes every other token; the current device stays signed in.' requestBody: required: true content: application/json: schema: required: - current_password - new_password properties: current_password: type: string format: password new_password: type: string format: password minLength: 8 type: object responses: '200': $ref: '#/components/responses/Success' '401': $ref: '#/components/responses/Unauthorized' '422': $ref: '#/components/responses/ValidationError' security: - bearerAuth: [] /password-forgot.php: post: tags: - Auth summary: 'Send a password reset code' description: 'Always answers 200, even for an unknown e-mail, so the endpoint cannot be used to enumerate accounts.' requestBody: required: true content: application/json: schema: required: - email properties: email: type: string format: email type: object responses: '200': $ref: '#/components/responses/Success' security: [] /password-reset.php: post: tags: - Auth summary: 'Reset the password with a 6-digit code' description: 'Revokes every token, so all devices are signed out. Error codes: code_invalid or code_expired.' requestBody: required: true content: application/json: schema: required: - email - code - password properties: email: type: string format: email code: type: string example: '123456' password: type: string format: password minLength: 8 type: object responses: '200': $ref: '#/components/responses/Success' '422': $ref: '#/components/responses/ValidationError' security: [] /register.php: post: tags: - Auth summary: 'Register an account and send a verification code' description: 'No token is issued: the e-mail must be confirmed via email-verify.php first.' requestBody: required: true content: application/json: schema: required: - email - password properties: email: type: string format: email password: type: string format: password minLength: 8 first_name: type: string last_name: type: string type: object responses: '201': $ref: '#/components/responses/Success' '422': $ref: '#/components/responses/ValidationError' security: [] /sections.php: get: tags: - Documents summary: 'Home document sections' parameters: - name: idhome in: query required: true schema: type: integer - name: slug in: query schema: type: string default: legal responses: '200': description: OK content: application/json: schema: properties: data: { type: array, items: { $ref: '#/components/schemas/Section' } } type: object '403': $ref: '#/components/responses/Forbidden' security: - bearerAuth: [] /share-delete.php: post: tags: - Sharing summary: 'Delete a sharing condition' requestBody: required: true content: application/json: schema: required: - idsharing properties: idsharing: type: integer type: object responses: '200': $ref: '#/components/responses/Success' '403': $ref: '#/components/responses/Forbidden' security: - bearerAuth: [] /share-respond.php: post: tags: - Sharing summary: 'Accept/decline an invitation' requestBody: required: true content: application/json: schema: required: - idsharing - status properties: idsharing: type: integer status: type: string enum: [accepted, rejected] type: object responses: '200': $ref: '#/components/responses/Success' '403': $ref: '#/components/responses/Forbidden' '422': $ref: '#/components/responses/ValidationError' security: - bearerAuth: [] /share-save.php: post: tags: - Sharing summary: 'Share a home by e-mail' description: 'status=accepted if the e-mail exists in auth_users, otherwise pending.' requestBody: required: true content: application/json: schema: required: - idhome - shared_email - sharing_type - role_id properties: idhome: type: integer shared_email: type: string format: email role_id: type: integer sharing_type: type: string enum: [read-only, add-documents, full-control] shared_sections: type: array items: { type: integer } expiration_date: type: string format: date type: object responses: '201': description: Created content: application/json: schema: properties: data: { $ref: '#/components/schemas/Share' } type: object '403': $ref: '#/components/responses/Forbidden' '422': $ref: '#/components/responses/ValidationError' security: - bearerAuth: [] /share-update.php: post: tags: - Sharing summary: 'Update a sharing condition' requestBody: required: true content: application/json: schema: required: - idsharing properties: idsharing: type: integer shared_email: type: string format: email role_id: type: integer sharing_type: type: string shared_sections: type: array items: { type: integer } expiration_date: type: string format: date type: object responses: '200': description: OK content: application/json: schema: properties: data: { $ref: '#/components/schemas/Share' } type: object '403': $ref: '#/components/responses/Forbidden' security: - bearerAuth: [] /shares-incoming.php: get: tags: - Sharing summary: 'Incoming invitations (shared with me)' responses: '200': description: OK content: application/json: schema: properties: data: { type: array, items: { $ref: '#/components/schemas/Share' } } type: object '401': $ref: '#/components/responses/Unauthorized' security: - bearerAuth: [] /shares.php: get: tags: - Sharing summary: 'Sharing conditions for my home' parameters: - name: idhome in: query required: true schema: type: integer responses: '200': description: OK content: application/json: schema: properties: data: { type: array, items: { $ref: '#/components/schemas/Share' } } type: object '403': $ref: '#/components/responses/Forbidden' security: - bearerAuth: [] /sharing-roles.php: get: tags: - Reference summary: 'Sharing roles reference' responses: '200': description: OK content: application/json: schema: properties: data: { type: array, items: { $ref: '#/components/schemas/SharingRole' } } type: object '401': $ref: '#/components/responses/Unauthorized' security: - bearerAuth: [] components: schemas: Error: properties: error: properties: code: type: string example: forbidden message: type: string fields: description: 'field -> array of errors (422 only)' type: object nullable: true type: object type: object User: properties: id: type: integer first_name: type: string nullable: true last_name: type: string nullable: true email: type: string format: email username: type: string nullable: true phone: type: string nullable: true address: type: string nullable: true avatar: description: 'absolute URL' type: string nullable: true type: object Home: properties: idhome: type: integer name: type: string nullable: true comment: type: string nullable: true fulladdress: type: string nullable: true address: type: string nullable: true zip: type: string nullable: true city: type: string nullable: true country: type: string nullable: true latitude: type: string nullable: true longitude: type: string nullable: true mainphoto: type: string nullable: true photo_url: type: string nullable: true cadastral_municipality: type: string nullable: true cadastral_section: type: string nullable: true cadastral_sheet: type: string nullable: true cadastral_particle: type: string nullable: true cadastral_sub: type: string nullable: true cadastral_category: type: string nullable: true cadastral_class: type: string nullable: true cadastral_surface: type: string nullable: true cadastral_rendita: type: string nullable: true cadastral_notes: type: string nullable: true is_owner: type: boolean documents_count: description: 'uploaded files for the property' type: integer expiring_count: description: 'expiring within 60 days or already expired' type: integer type: object Section: properties: idsections: type: integer section_name: type: string description: type: string nullable: true type: object UploadedFile: properties: id: type: integer document_id: type: integer title: description: 'user-defined display name; falls back to the requirement name' type: string nullable: true idhome: type: integer nullable: true owner_id: type: integer nullable: true filename: type: string size: description: 'bytes on disk, null if missing' type: integer nullable: true url: type: string expiry_date: type: string format: date nullable: true expiry_status: description: 'tinyint 0/1' type: integer nullable: true note: type: string nullable: true created_at: type: string nullable: true type: object DocumentRequirement: description: 'Document template (documents) plus its uploaded files' properties: document_id: type: integer document_name: type: string page_id: type: integer nullable: true idsections: type: integer nullable: true section_name: type: string nullable: true max_documents: type: integer is_required: type: boolean notes: type: string nullable: true files: type: array items: $ref: '#/components/schemas/UploadedFile' type: object DocumentTemplate: properties: document_id: type: integer document_name: type: string page_id: type: integer nullable: true idsections: type: integer nullable: true section_name: type: string nullable: true max_documents: type: integer is_required: type: boolean notes: type: string nullable: true type: object Page: properties: idpages: type: integer namepages: type: string slug: type: string descriptionpages: type: string nullable: true type: object Owner: properties: owner_id: type: integer owner_type: type: string enum: - individual - company first_name: type: string nullable: true last_name: type: string nullable: true company_name: type: string nullable: true tax_code: type: string email: type: string nullable: true phone: type: string nullable: true address: type: string nullable: true postal_code: type: string nullable: true city: type: string nullable: true province: type: string nullable: true country: type: integer nullable: true role: type: string nullable: true type: object HomeOwner: properties: owner: $ref: '#/components/schemas/Owner' ownership_percentage: type: number format: float notes: type: string nullable: true type: object SharingRole: properties: idrole: type: integer role_name: type: string description: type: string nullable: true permissions: type: array items: type: string type: object Share: properties: idsharing: type: integer idhome: type: integer shared_email: type: string format: email idshareduser: type: integer nullable: true role: oneOf: - $ref: '#/components/schemas/SharingRole' nullable: true sharing_type: type: string shared_sections: type: array items: type: integer expiration_date: type: string format: date nullable: true status: type: string enum: - pending - accepted - rejected type: object responses: Unauthorized: description: 'Missing/invalid token' content: application/json: schema: $ref: '#/components/schemas/Error' Forbidden: description: 'No access to resource' content: application/json: schema: $ref: '#/components/schemas/Error' NotFound: description: 'Not found' content: application/json: schema: $ref: '#/components/schemas/Error' ValidationError: description: 'Validation error' content: application/json: schema: $ref: '#/components/schemas/Error' Success: description: Success content: application/json: schema: properties: success: type: boolean example: true type: object securitySchemes: bearerAuth: type: http description: 'Token from POST /login.php. Header: Authorization: Bearer ' scheme: bearer tags: - name: Auth description: 'Login, tokens, current user' - name: Homes description: Properties - name: Documents description: 'Document requirements, files, sections' - name: Owners description: 'Owners and home links' - name: Sharing description: 'Home sharing and invitations' - name: Reference description: 'Read-only reference data'