# MyDesk Core API Reference Complete reference for all endpoints in `routes/api.php`. **Live docs:** `http://localhost:8000/docs/api-reference` --- ## Base URL ``` http://localhost:8000/api ``` Replace with your deployment URL in production (e.g. `https://api.mydesk.com/api`). --- ## Authentication Every request requires a **Core Token** (app-to-app secret). ### Core Token (required on all routes) | Header / field | Example | |----------------|---------| | `X-Core-Token` | `your_core_token` | | `Core-Token` | `your_core_token` | | Body/query `core_token` | `your_core_token` | Set in `.env` as `CORE_TOKEN`. ### API Token (protected routes only) Protected routes also require a **Vendor** or **Admin** API token: | Header | Example | |--------|---------| | `Authorization` | `Bearer {api_token}` | | `X-API-Token` | `{api_token}` | | Caller | Token source | |--------|----------------| | **Vendor** | `vendors.api_token` (issued on login / email verify) | | **Admin** | Plaintext token from `POST /admin/login` (stored hashed in `admins.remember_token`) | > **Important:** After vendor login with organizations, `data.session.core_access_token` is the token for **Core API** (`/api/*`). Top-level `data.api_token` is the **workspace** token from the organization database — use it for the tenant app, not core admin routes. ### Auth levels | Level | Middleware | Endpoints | |-------|------------|-----------| | **Public** | `core.token` | Login, register, invitations, org-stats, etc. | | **Protected** | `core.token` + `auth.api` | Organizations, vendors, plans, invoices, roles, admins | --- ## Response formats Most endpoints use the **MessageService** envelope: ```json { "status": 200, "success": true, "message": "Human-readable message", "data": {} } ``` Paginated lists add: ```json { "payload": { "pagination": { "page": 1, "last_page": 5, "items_per_page": "10", "total": 50, "from": 1, "to": 10, "next_page_url": "...", "prev_page_url": null, "links": [] } } } ``` **Legacy Admin / Role** endpoints may return `"status": "200"` (string) and omit `success`. **Validation error (422):** ```json { "status": 422, "success": false, "message": "Validation failed", "errors": { "email": ["The email field is required."] } } ``` --- ## Quick endpoint index | # | Method | Path | Auth | |---|--------|------|------| | 1 | POST | `/admin/login` | Public | | 2 | POST | `/admin/verify-token` | Public | | 3 | POST | `/login` | Public | | 4 | POST | `/otp-verify` | Public | | 5 | POST | `/verify_token` | Public | | 6 | POST | `/core-login` | Public | | 7 | POST | `/register` | Public | | 8 | POST | `/vendor/send-reset-password` | Public | | 9 | POST | `/reset-password` | Public | | 10 | GET | `/verify-email` | Public | | 11 | POST | `/resend-verification` | Public (vendor email) | | 12 | POST | `/invite-vendor` | Public | | 13 | POST | `/vendor-invitations/accept` | Public | | 14 | POST | `/notify` | Public | | 15 | POST | `/org-stats` | Public | | 16 | GET | `/roles` | Protected | | 17 | POST | `/role` | Protected | | 18 | GET | `/role/{id}` | Protected | | 19 | PUT | `/role/{id}` | Protected | | 20 | DELETE | `/role/{id}` | Protected | | 21 | POST | `/admin/{id}/assign-role` | Protected | | 22 | GET | `/admins` | Protected | | 23 | POST | `/admin` | Protected | | 24 | GET | `/admin/{id}` | Protected | | 25 | PUT | `/admin/{id}` | Protected | | 26 | DELETE | `/admin/{id}` | Protected | | 27 | POST | `/admin/send-reset-link` | Protected | | 28 | POST | `/admin/password/reset/{token}` | Public | | 29 | GET | `/organizations` | Protected | | 30 | POST | `/organization` | Protected | | 31 | POST | `/organization/update/{orgcode}` | Protected | | 32 | DELETE | `/organization/{orgcode}` | Protected | | 33 | POST | `/organization/action/{orgcode}` | Protected | | 34 | POST | `/send-organization-invitations` | Protected | | 35 | POST | `/organization/team-members` | Protected | | 36 | GET | `/organization/{orgcode}` | Protected | | 37 | POST | `/check-organization-access` | Protected | | 38 | POST | `/organization/{orgcode}/upload-logo` | Protected | | 39 | POST | `/organization/{orgcode}/upload-file` | Protected | | 40 | GET | `/organization/{orgcode}/files` | Protected | | 41 | DELETE | `/organization/{orgcode}/file` | Protected | | 42 | GET | `/organizations-list` | Protected | | 43 | GET | `/vendors` | Protected | | 44 | GET | `/vendor/{id}` | Protected | | 45 | PUT | `/vendor/{id}` | Protected | | 46 | DELETE | `/vendor/{id}` | Protected | | 47 | POST | `/vendor/activate/{id}` | Protected | | 48 | POST | `/vendor/deactivate/{id}` | Protected | | 49 | POST | `/vendor/delete/{id}` | Protected | | 50 | POST | `/vendor/delete_permanently/{id}` | Protected | | 51 | GET | `/plans` | Protected | | 52 | GET | `/plan/{id}` | Protected | | 53 | POST | `/plan` | Protected (admin) | | 54 | PUT | `/plan/{id}` | Protected (admin) | | 55 | DELETE | `/plan/{id}` | Protected (admin) | | 56 | POST | `/vendor/{vendorId}/assign-plan` | Protected (admin) | | 57 | GET | `/vendor/{vendorId}/plan` | Protected | | 58 | GET | `/invoices` | Protected | | 59 | GET | `/invoice/{id}` | Protected | | 60 | POST | `/invoice` | Protected (admin) | | 61 | POST | `/invoices/generate` | Protected (admin) | | 62 | PUT | `/invoice/{id}` | Protected (admin) | | 63 | POST | `/invoice/{id}/mark-paid` | Protected (admin) | | 64 | POST | `/invoice/{id}/cancel` | Protected (admin) | | 65 | POST | `/invoice/{id}/void` | Protected (admin) | | 66 | DELETE | `/invoice/{id}` | Protected (admin) | | 67 | GET | `/vendor/{vendorId}/invoices` | Protected | --- ## Authentication Endpoints ### 1. Admin Login **POST** `/admin/login` · Public **Request body:** ```json { "email": "admin@mydesk.com", "password": "Admin@123456" } ``` **Success (200):** ```json { "status": "200", "message": "Login successful", "data": { "token": "plaintext_admin_token_use_as_bearer" } } ``` **cURL:** ```bash curl -X POST http://localhost:8000/api/admin/login \ -H "Content-Type: application/json" \ -H "X-Core-Token: your_core_token" \ -d '{"email":"admin@mydesk.com","password":"Admin@123456"}' ``` --- ### 2. Admin Verify Token **POST** `/admin/verify-token` · Public **Request body:** ```json { "token": "plaintext_admin_token" } ``` **Success (200):** ```json { "status": "200", "message": "Token is valid", "data": { "name": "Admin", "email": "admin@mydesk.com", "role_id": 1, "status": 1 } } ``` --- ### 3. Vendor Login **POST** `/login` · Public **Request body (password):** ```json { "email": "vendor@example.com", "action": "password", "password": "SecurePass1!" } ``` **Request body (OTP request):** ```json { "email": "vendor@example.com", "action": "otp" } ``` **Success — password login with organizations (200):** ```json { "status": 200, "success": true, "message": "Login successful", "data": { "user": { "id": 1, "email": "vendor@example.com", "name": "Jane Doe", "is_active": true }, "session": { "core_access_token": "core_api_token_for_protected_routes", "access_token": "workspace_token_from_org_db", "token_type": "Bearer", "organization_code": "1588571752", "database": "mydesk_1588571752" }, "api_token": "workspace_token_from_org_db", "organizations": [ { "id": 1, "code": "1588571752", "name": "Acme Corp", "status": "ready", "api_token": "workspace_token_from_org_db", "database": "mydesk_1588571752" } ], "meta": { "total_organizations": 1, "ready_organizations": 1 } } } ``` **Success — password login, no organization yet (200):** ```json { "status": 200, "success": true, "message": "Login successful! Let's start to setup your first organization.", "data": { "user": { "id": 1, "email": "vendor@example.com", "name": null, "is_active": true }, "session": { "access_token": "core_api_token", "token_type": "Bearer", "scope": "core" }, "api_token": "core_api_token", "organizations": [] }, "requires_organization": true, "action": "create_organization" } ``` **Success — OTP requested (200):** ```json { "status": 200, "success": true, "message": "OTP has been sent to your registered email address.", "action": "verify_otp", "expires_in": "10 minutes" } ``` **Errors:** `404` email not found · `401` inactive / suspended / wrong password --- ### 4. OTP Verify **POST** `/otp-verify` · Public **Request body:** ```json { "email": "vendor@example.com", "otp": "123456" } ``` **Success (200):** Same shape as password login (`completeAuthenticatedLogin`). **Errors:** `401` invalid or expired OTP --- ### 5. Vendor Token Verify **POST** `/verify_token` · Public **Request body:** ```json { "api_token": "vendor_core_api_token" } ``` **Success (200):** ```json { "status": 200, "success": true, "message": "Token is valid", "data": { "full_name": "Jane Doe", "avatar": null, "email": "vendor@example.com" } } ``` --- ### 6. Core Login **POST** `/core-login` · Public Alternative login that issues core + workspace session tokens (no OTP). **Request body:** ```json { "email": "vendor@example.com", "password": "SecurePass1!" } ``` **Success (200):** ```json { "status": 200, "success": true, "message": "Success", "data": { "user": { "id": 1, "email": "vendor@example.com", "name": "Jane Doe" }, "session": { "core_access_token": "core_api_token", "access_token": "workspace_token", "token_type": "Bearer", "organization_code": "1588571752", "database": "mydesk_1588571752" }, "api_token": "workspace_token", "organizations": [], "meta": {} } } ``` --- ## Vendor Registration & Account ### 7. Register Vendor **POST** `/register` · Public **Request body:** ```json { "email": "newvendor@example.com", "password": "SecurePass1!", "password_confirmation": "SecurePass1!" } ``` Password rules: min 8, mixed case, numbers, symbols, uncompromised. **Success (200):** ```json { "status": 200, "success": true, "message": "Registration successful! Please check your email for verification.", "data": { "email": "newvendor@example.com" } } ``` New vendors are auto-assigned the **Free** plan. --- ### 8. Send Password Reset Link **POST** `/vendor/send-reset-password` · Public **Request body:** ```json { "email": "vendor@example.com" } ``` **Success (200):** ```json { "status": 200, "success": true, "message": "Password reset link sent successfully to your email..." } ``` --- ### 9. Reset Password **POST** `/reset-password` · Public **Request body:** ```json { "token": "reset_token_from_email", "password": "NewSecurePass1!", "password_confirmation": "NewSecurePass1!" } ``` **Success (200):** ```json { "status": 200, "success": true, "message": "Password reset successfully!" } ``` --- ### 10. Verify Email **GET** `/verify-email?token={verification_token}` · Public **Success (200):** ```json { "status": 200, "success": true, "message": "Congratulations! Your account has been successfully verified.", "data": { "api_token": "new_core_api_token", "user": { "id": 1, "full_name": null, "email": "vendor@example.com", "is_active": true } }, "requires_organization": true, "next_step": "create_organization" } ``` --- ### 11. Resend Email Verification **POST** `/resend-verification` · Public > **Note:** This route is registered twice in `api.php`. The public vendor resend (registered first) takes precedence. The protected organization resend on the same path is unreachable. **Request body:** ```json { "email": "vendor@example.com" } ``` **Success (200):** ```json { "status": 200, "success": true, "message": "Verification email has been sent successfully! Please check your inbox.", "data": { "email": "vendor@example.com" } } ``` --- ## Vendor Management (Protected) ### 12. List Vendors **GET** `/vendors` · Protected **Query parameters:** | Param | Type | Description | |-------|------|-------------| | `items_per_page` | int | Default `10` | | `filter_status` | string | `1`, `active`, `true` = active only | | `filter_organization_id` | int | Vendors linked to org | | `search` | string | Search `full_name` or `email` | | `sort` | string | `id`, `name`, `full_name`, `email`, `created_at` | | `order` | string | `asc` or `desc` | **Example:** `GET /api/vendors?items_per_page=20&filter_status=active&search=jane` **Success (200):** ```json { "status": 200, "success": true, "message": "Records listed successfully!", "data": [ { "id": 1, "email": "vendor@example.com", "full_name": "Jane Doe", "is_active": true, "plan_id": 1, "billing_day": 1, "created_at": "2026-01-15T10:00:00.000000Z" } ], "payload": { "pagination": { "page": 1, "total": 1 } } } ``` --- ### 13. Get Vendor **GET** `/vendor/{id}` · Protected **Success (200):** ```json { "status": 200, "success": true, "message": "Record retrieved successfully!", "data": { "full_name": "Jane Doe", "email": "vendor@example.com", "is_active": true, "deleted_at": null, "avatar": null } } ``` --- ### 14. Update Vendor **PUT** `/vendor/{id}` · Protected **Request body:** ```json { "email": "updated@example.com", "full_name": "Jane Smith", "password": "NewSecurePass1!", "is_active": true, "avatar": "https://cdn.example.com/avatar.jpg" } ``` | Field | Rules | |-------|-------| | `email` | required, unique | | `full_name` | required | | `password` | optional, min 8 | | `is_active` | required boolean | | `avatar` | optional string | --- ### 15. Delete Vendor **DELETE** `/vendor/{id}` · Protected Soft-deletes the vendor. --- ### 16. Vendor Operations **POST** `/vendor/activate/{id}` · Protected **POST** `/vendor/deactivate/{id}` · Protected **POST** `/vendor/delete/{id}` · Protected (soft delete) **POST** `/vendor/delete_permanently/{id}` · Protected (force delete) No request body required. **Success (200):** ```json { "status": 200, "success": true, "message": "Vendor activated successfully!" } ``` --- ## Organization Management (Protected) ### 17. Create Organization **POST** `/organization` · Protected Provisions a new tenant database immediately. **Request body:** ```json { "organization_name": "Acme Corp", "full_name": "Jane Doe", "layout_preference": "grid", "plan": "free", "database_plan": "separate_db_same_host", "dbserver": "self", "vendor_id": 1 } ``` | Field | Rules | |-------|-------| | `organization_name` | optional string | | `full_name` | **required** string | | `layout_preference` | `grid`, `list`, or `table` | | `plan` / `vendor_plan` | `team`, `standard`, `free`, `enterprise`, `business`, `shared`, `free_shared` (maps to database tenancy) | | `database_plan` | `shared_db`, `separate_db_same_host`, `separate_db_separate_host` | | `vendor_id` | optional; defaults to authenticated vendor | **Success (200):** ```json { "status": 200, "success": true, "message": "Success", "data": { "orgcode": "1588571752", "name": "Acme Corp" } } ``` **cURL:** ```bash curl -X POST http://localhost:8000/api/organization \ -H "Content-Type: application/json" \ -H "X-Core-Token: your_core_token" \ -H "Authorization: Bearer vendor_core_token" \ -d '{"organization_name":"Acme Corp","full_name":"Jane Doe","plan":"free"}' ``` --- ### 18. List Organizations **GET** `/organizations` · Protected **Query:** `items_per_page`, `organization_id`, `filter_status`, `search`, `sort`, `order` **Admin response** — paginated with `vendor_name`, `deleted_at`, etc. **Vendor response** — unpaginated list scoped to owned/member orgs: ```json { "status": 200, "success": true, "message": "Organizations listed successfully", "data": [ { "name": "Acme Corp", "orgcode": "1588571752", "email": "org@acme.com", "logo": "https://...", "is_active": true, "status_label": "Active", "self": true } ] } ``` --- ### 19. Get Organization **GET** `/organization/{orgcode}` · Protected **Success (200):** ```json { "status": 200, "success": true, "message": "Organization retrieved successfully!", "data": { "id": 1, "name": "Acme Corp", "orgcode": "1588571752", "email": "org@acme.com", "phone": null, "address": null, "website": null, "logo": "https://...", "status": 1 } } ``` --- ### 20. Update Organization **POST** `/organization/update/{orgcode}` · Protected **Request body:** ```json { "organization_name": "Acme Corporation", "full_name": "Jane Doe", "is_active": true, "layout_preference": "list" } ``` --- ### 21. Delete Organization **DELETE** `/organization/{orgcode}` · Protected Permanently deletes organization and tenant database. --- ### 22. Organization Quick Actions **POST** `/organization/action/{orgcode}` · Protected **Request body:** ```json { "action": "soft_delete" } ``` Allowed: `soft_delete`, `activate`, `deactivate` --- ### 23. Send Organization Invitations **POST** `/send-organization-invitations` · Protected **Request body:** ```json { "emails": "user1@example.com, user2@example.com", "organization_id": 1 } ``` **Success (200):** ```json { "status": 200, "success": true, "message": "Invitations sent successfully.", "sent_count": 2 } ``` Email-only invitations (does **not** create a vendor row or bind to `organization_vendor`). Prefer [Add Organization Team Member](#add-organization-team-member) when the teammate should become a vendor linked to the org. --- ### Add Organization Team Member **POST** `/organization/team-members` · Protected Create (or fetch) a vendor by email, bind them to the organization via `organization_vendor`, and send a vendor invitation email. The calling vendor must already have access to the target organization. **Request body:** ```json { "email": "teammate@example.com", "orgcode": "1588571752", "full_name": "Jane Doe" } ``` | Field | Required | Notes | |-------|----------|-------| | `email` | Yes | Team member email; creates `vendors` row if missing | | `orgcode` | One of `orgcode` / `organization_id` | Target organization shortcode | | `organization_id` | One of `orgcode` / `organization_id` | Target organization id | | `full_name` | No | Display name for new vendor | | `approve_token` | No | Opaque token from org app; generated if omitted | **Success (200):** ```json { "status": 200, "success": true, "message": "Team member added and bound to the organization successfully!", "data": { "vendor_id": 2, "vendor_email": "teammate@example.com", "vendor_full_name": "Jane Doe", "organization_id": 1, "orgcode": "1588571752", "already_linked": false, "invitation_id": 5, "invitation_url": "http://localhost:3000/1588571752/uuid-token", "mail_sent": true } } ``` **cURL:** ```bash curl -X POST http://localhost:8000/api/organization/team-members \ -H "Content-Type: application/json" \ -H "X-Core-Token: your_core_token" \ -H "Authorization: Bearer vendor_api_token" \ -d '{"email":"teammate@example.com","orgcode":"1588571752","full_name":"Jane Doe"}' ``` **Notes:** - Invitee accepts via `POST /vendor-invitations/accept` (see [Vendor Invitations](#vendor-invitations-public)). - Invitation email links to `{FRONTEND_URL}/accept-invitation/{orgcode}/{token}` on the marketing site (mydesk-website). - Re-inviting the same email refreshes the invitation token and re-sends the email. - Invitation email is sent via `VendorInvitationMail` using `FRONTEND_URL` for the accept link (SPA Origin is ignored). - Related public endpoint: `POST /invite-vendor` (core token only; no caller membership check). --- ### 25. Check Organization Access **POST** `/check-organization-access` · Protected **Request body:** ```json { "orgcode": "1588571752" } ``` **Success (200):** ```json { "status": 200, "success": true, "message": "Access granted", "data": { "vendor_id": 1, "organization_id": 1, "token": "workspace_access_token" } } ``` --- ### 26. My Organizations List **GET** `/organizations-list` · Protected **Success (200):** ```json { "status": 200, "success": true, "message": "Organizations listed successfully", "data": [ { "id": 1, "name": "Acme Corp", "vendor_id": 1 } ] } ``` --- ### 27. Organization Stats **POST** `/org-stats` · Public **Request body:** ```json { "orgcode": "1588571752" } ``` **Success (200):** ```json { "organization": { "name": "Acme Corp", "orgcode": "1588571752" }, "db": { "size_mb": 12.5, "tables": 40 }, "projects": { "all": 10, "active": 8, "inactive": 2 }, "tasklists": { "all": 20, "active": 15, "inactive": 5 }, "users": { "all": 5, "active": 4, "inactive": 1 } } ``` --- ## Organization File Management (Protected) ### 28. Upload Logo **POST** `/organization/{orgcode}/upload-logo` · Protected **Content-Type:** `multipart/form-data` | Field | Rules | |-------|-------| | `logo` | required file, max 100 KB | **Success (200):** ```json { "status": 200, "success": true, "message": "Organization logo uploaded successfully!", "data": { "logo_path": "organizations/1588571752/logos/logo.png", "logo_url": "https://cdn.example.com/organizations/1588571752/logos/logo.png" } } ``` --- ### 29. Upload File **POST** `/organization/{orgcode}/upload-file` · Protected **Content-Type:** `multipart/form-data` | Field | Rules | |-------|-------| | `file` | required file, max 100 KB | | `folder` | optional: `documents`, `images`, `temp` | --- ### 30. List Files **GET** `/organization/{orgcode}/files?folder=documents` · Protected **Success (200):** ```json { "status": 200, "success": true, "message": "Organization files listed successfully!", "data": { "files": [ { "name": "report.pdf", "path": "organizations/1588571752/documents/report.pdf", "url": "https://...", "size": 2048, "last_modified": 1704067200 } ], "folder": "documents", "total_files": 1 } } ``` --- ### 31. Delete File **DELETE** `/organization/{orgcode}/file` · Protected **Request body:** ```json { "file_path": "organizations/1588571752/documents/report.pdf" } ``` --- ## Admin Management (Protected) ### 31. List Admins **GET** `/admins` · Protected **Query:** `items_per_page`, `filter_role`, `filter_status`, `search`, `sort`, `order` **Success (200):** ```json { "status": 200, "success": true, "message": "Records listed successfully!", "data": [ { "id": 1, "name": "Admin", "email": "admin@mydesk.com", "role_name": "Admin" } ], "payload": { "pagination": { "page": 1, "total": 1 } } } ``` --- ### 32. Create Admin **POST** `/admin` · Protected **Request body:** ```json { "name": "New Admin", "email": "newadmin@example.com", "password": "password123", "role_id": 1, "status": true, "avatar": null } ``` --- ### 33. Get Admin **GET** `/admin/{id}` · Protected --- ### 34. Update Admin **PUT** `/admin/{id}` · Protected **Request body:** `name`, `email`, `avatar`, `password` (min 6), `role_id`, `status` — all optional --- ### 35. Delete Admin **DELETE** `/admin/{id}?action=soft_delete` · Protected **Query `action`:** `soft_delete`, `activate`, `deactivate`, `permanent_delete` --- ### 36. Send Admin Reset Link **POST** `/admin/send-reset-link` · Protected **Request body:** ```json { "email": "admin@mydesk.com" } ``` --- ### 37. Reset Admin Password **POST** `/admin/password/reset/{token}` · Public **Request body:** ```json { "email": "admin@mydesk.com", "password": "newpassword123", "password_confirmation": "newpassword123" } ``` --- ## Role Management (Protected) ### 38. List Roles **GET** `/roles?items_per_page=10` · Protected --- ### 39. Create Role **POST** `/role` · Protected **Request body:** ```json { "name": "Manager", "description": "Can manage projects and members" } ``` --- ### 40. Get Role **GET** `/role/{id}` · Protected --- ### 41. Update Role **PUT** `/role/{id}` · Protected --- ### 42. Delete Role **DELETE** `/role/{id}` · Protected --- ### 43. Assign Role to Admin **POST** `/admin/{id}/assign-role` · Protected **Request body:** ```json { "role_id": 1 } ``` --- ## Vendor Invitations (Public) For the authenticated frontend flow (logged-in vendor adds a teammate by email), use [Add Organization Team Member](#add-organization-team-member) (`POST /organization/team-members`). The endpoints below are the legacy/public invite path and acceptance. ### 44. Create Vendor Invitation **POST** `/invite-vendor` · Public Creates or fetches a vendor, binds them to the organization, and emails an invitation. Does **not** verify that the caller belongs to the organization — prefer the protected team-members endpoint for product UI. **Request body:** ```json { "email": "collaborator@example.com", "orgcode": "1588571752", "full_name": "John Collaborator", "approve_token": "approval_token_from_org_app" } ``` Alternative name fields: `first_name` + `last_name` instead of `full_name`. **Success (200):** ```json { "status": 200, "success": true, "message": "Invitation sent successfully!", "data": { "vendor_id": 2, "organization_id": 1, "invitation_url": "http://app/1588571752/uuid-token" } } ``` --- ### 45. Accept Vendor Invitation **POST** `/vendor-invitations/accept` · Public **Request body:** ```json { "invitation_token": "uuid-from-invitation-url", "orgcode": "1588571752", "password": "ChooseASecurePass1!", "password_confirmation": "ChooseASecurePass1!" } ``` **Success (200):** ```json { "status": 200, "success": true, "message": "Invitation accepted successfully!", "data": { "vendor_id": 2, "org_id": 1, "orgcode": "1588571752", "email": "teammate@example.com", "accepted_at": "2026-07-15T10:00:00.000000Z", "api_token": "new_vendor_core_token" } } ``` **Notes:** - The invitee sets their own password on the accept page; it is stored on `vendors.password` and synced to the org `user.password`. - Then they sign in with that email + password. --- ## Notifications (Public) ### 46. Send Notification **POST** `/notify` · Public Broadcasts via Laravel Reverb to organization users. **Request body:** ```json { "user_ids": [1, 2, 3], "orgcode": "1588571752", "title": "New task assigned", "url": "https://app.mydesk.com/tasks/42", "message": "You have been assigned to task #42" } ``` **Success (200):** ```json { "status": "success", "data": { "title": "New task assigned", "url": "https://app.mydesk.com/tasks/42", "message": "You have been assigned to task #42", "orgcode": "1588571752" }, "user_ids": [1, 2, 3] } ``` --- ## Plan Management (Protected) Default seeded plans: | Plan | Price | Team | Storage | |------|-------|------|---------| | Free | $0/mo | Max 5 | 500 MB | | Personal | $5/user | Unlimited | 5 GB | | Business | $3.49/user | Unlimited | 30 GB | ### 47. List Plans **GET** `/plans` · Protected **Query:** `items_per_page`, `search`, `active_only` (admin only) Vendors see active plans only. Admins see all unless `active_only=true`. **Success (200):** ```json { "status": 200, "success": true, "message": "Plans listed successfully!", "data": [ { "id": 1, "name": "Free", "slug": "free", "description": "Get started with limited team size...", "price_monthly": 0, "price_per_user": 0, "max_team_members": 5, "max_storage_mb": 500, "max_storage_gb": 0.49, "features": { "unlimited_projects": true, "unlimited_tasklists": true, "unlimited_tasks": true, "unlimited_statuses": true, "unlimited_roles": true }, "is_active": true, "sort_order": 1 } ], "payload": { "pagination": { "page": 1, "total": 3 } } } ``` --- ### 48. Get Plan **GET** `/plan/{id}` · Protected --- ### 49. Create Plan (Admin only) **POST** `/plan` · Protected · Admin **Request body:** ```json { "name": "Enterprise", "slug": "enterprise", "description": "Custom enterprise plan", "price_monthly": 99.00, "price_per_user": 2.99, "max_team_members": null, "max_storage_mb": 102400, "is_active": true, "sort_order": 4 } ``` `null` on limit fields = unlimited. --- ### 50. Update Plan (Admin only) **PUT** `/plan/{id}` · Protected · Admin --- ### 51. Delete Plan (Admin only) **DELETE** `/plan/{id}` · Protected · Admin Fails with `400` if vendors are still assigned. --- ### 52. Assign Plan to Vendor (Admin only) **POST** `/vendor/{vendorId}/assign-plan` · Protected · Admin **Request body:** ```json { "plan_id": 2, "billing_day": 15 } ``` `billing_day`: 1–28 (day of month for recurring invoices). **Success (200):** ```json { "status": 200, "success": true, "message": "Plan assigned to vendor successfully!", "data": { "vendor_id": 1, "plan": { "id": 2, "name": "Personal", "slug": "personal" }, "plan_started_at": "2026-07-15T10:00:00.000000Z", "billing_day": 15 } } ``` --- ### 53. Get Vendor Plan **GET** `/vendor/{vendorId}/plan` · Protected Vendor can only access own plan. Admin can access any vendor. --- ## Invoice Management (Protected) Invoice statuses: `draft`, `pending`, `paid`, `overdue`, `cancelled`, `void` Generation types: `automatic`, `manual` ### 54. List Invoices **GET** `/invoices` · Protected **Query:** | Param | Description | |-------|-------------| | `items_per_page` | Default `10` | | `vendor_id` | Admin filter | | `status` | `pending`, `paid`, etc. | | `generation_type` | `automatic` or `manual` | | `search` | Invoice number or plan name | | `period_start` / `period_end` | Date filters | Vendor sees own invoices only. --- ### 55. Get Invoice **GET** `/invoice/{id}` · Protected **Success (200):** ```json { "status": 200, "success": true, "message": "Invoice retrieved successfully!", "data": { "id": 1, "invoice_number": "INV-202607-00001", "vendor_id": 1, "vendor": { "id": 1, "email": "vendor@example.com", "full_name": "Jane Doe" }, "plan_id": 2, "plan_name": "Personal", "period_start": "2026-07-01", "period_end": "2026-07-31", "team_members_count": 8, "price_per_user": 5.00, "base_amount": 0.00, "seats_amount": 40.00, "subtotal": 40.00, "tax_rate": 0, "tax_amount": 0.00, "total": 40.00, "currency": "USD", "status": "pending", "generation_type": "automatic", "due_date": "2026-08-14", "paid_at": null, "items": [ { "id": 1, "description": "Personal plan — team members", "quantity": 8, "unit_price": 5.00, "amount": 40.00, "item_type": "seats" } ] } } ``` --- ### 56. Create Invoice Manually (Admin only) **POST** `/invoice` · Protected · Admin **Request body:** ```json { "vendor_id": 1, "period_start": "2026-07-01", "period_end": "2026-07-31", "team_members_count": 8, "tax_rate": 0, "notes": "July billing", "status": "pending", "due_date": "2026-08-14" } ``` --- ### 57. Generate Invoices (Admin only) **POST** `/invoices/generate` · Protected · Admin **Single vendor:** ```json { "vendor_id": 1, "generation_type": "manual", "period_start": "2026-07-01", "period_end": "2026-07-31" } ``` **All vendors (bulk monthly):** ```json { "all_vendors": true, "period_start": "2026-07-01" } ``` **Bulk success (200):** ```json { "status": 200, "success": true, "message": "Bulk invoice generation completed!", "data": { "generated": [ { "vendor_id": 1, "invoice_id": 1, "invoice_number": "INV-202607-00001", "total": 40.00 } ], "skipped": [], "failed": [] } } ``` **CLI equivalent:** ```bash php artisan invoices:generate --month=2026-07 --mark-overdue ``` --- ### 58. Update Invoice (Admin only) **PUT** `/invoice/{id}` · Protected · Admin **Request body:** ```json { "notes": "Updated seat count", "team_members_count": 10, "tax_rate": 8.5, "due_date": "2026-08-20", "status": "pending" } ``` Only `draft` and `pending` invoices are editable. --- ### 59. Mark Invoice Paid (Admin only) **POST** `/invoice/{id}/mark-paid` · Protected · Admin **Request body:** ```json { "paid_at": "2026-07-20T14:30:00Z" } ``` --- ### 60. Cancel Invoice (Admin only) **POST** `/invoice/{id}/cancel` · Protected · Admin **Request body (optional):** ```json { "notes": "Customer requested cancellation" } ``` --- ### 61. Void Invoice (Admin only) **POST** `/invoice/{id}/void` · Protected · Admin --- ### 62. Delete Invoice (Admin only) **DELETE** `/invoice/{id}` · Protected · Admin Soft-delete. Paid invoices cannot be deleted — void instead. --- ### 63. List Vendor Invoices **GET** `/vendor/{vendorId}/invoices` · Protected Same as `GET /invoices` scoped to one vendor. Vendor can only access own ID. --- ## Error Responses | HTTP | When | Example | |------|------|---------| | 401 | Missing/invalid core or API token | `{ "success": false, "status": 401, "message": "Invalid or missing core token" }` | | 403 | Forbidden (e.g. vendor calling admin-only) | `{ "status": 403, "success": false, "message": "Access forbidden" }` | | 404 | Resource not found | `{ "status": 404, "success": false, "message": "Plan not found" }` | | 422 | Validation failed | See validation format above | | 500 | Server error | `{ "status": 500, "success": false, "message": "Something went wrong.", "error": "..." }` | --- ## Standard request headers ```bash # All requests -H "X-Core-Token: your_core_token" -H "Content-Type: application/json" # Protected routes (vendor) -H "Authorization: Bearer vendor_core_api_token" # Protected routes (admin) -H "Authorization: Bearer admin_token_from_login" ``` --- ## Testing workflow ```bash # 1. Register curl -X POST http://localhost:8000/api/register \ -H "X-Core-Token: $CORE_TOKEN" \ -H "Content-Type: application/json" \ -d '{"email":"test@example.com","password":"SecurePass1!","password_confirmation":"SecurePass1!"}' # 2. Verify email (from email link token) curl "http://localhost:8000/api/verify-email?token=VERIFICATION_TOKEN" \ -H "X-Core-Token: $CORE_TOKEN" # 3. Login curl -X POST http://localhost:8000/api/login \ -H "X-Core-Token: $CORE_TOKEN" \ -H "Content-Type: application/json" \ -d '{"email":"test@example.com","action":"password","password":"SecurePass1!"}' # 4. Create organization (use core_access_token from login) curl -X POST http://localhost:8000/api/organization \ -H "X-Core-Token: $CORE_TOKEN" \ -H "Authorization: Bearer $CORE_TOKEN" \ -H "Content-Type: application/json" \ -d '{"organization_name":"My Workspace","full_name":"Test User","plan":"free"}' # 5. List plans curl http://localhost:8000/api/plans \ -H "X-Core-Token: $CORE_TOKEN" \ -H "Authorization: Bearer $CORE_TOKEN" # 6. Admin login curl -X POST http://localhost:8000/api/admin/login \ -H "X-Core-Token: $CORE_TOKEN" \ -H "Content-Type: application/json" \ -d '{"email":"admin@mydesk.com","password":"Admin@123456"}' ``` --- ## Known quirks 1. **Two response styles** — Admin/Role use string `"status": "200"`; newer endpoints use integer `status` + `success`. 2. **Duplicate route** — `POST /resend-verification` registered for both vendor (public) and organization (protected); public wins. 3. **Token confusion** — After login with orgs, use `session.core_access_token` for Core API, not top-level `api_token` (workspace token). 4. **Organization `plan` field** — Maps to database tenancy (`database_plan`), separate from billing Plans (`/plans`). --- ## Related docs - [Organization Database Configuration](./ORGANIZATION_DATABASE_CONFIG.md) - [Multi-Tenancy Architecture](./MULTI_TENANCY_ARCHITECTURE.md) - [Database Seeders Guide](./DATABASE_SEEDERS_GUIDE.md)