API Reference
Table of Contents
- Base URL
- Authentication
- Response Format
- Quick Endpoint Index
- ๐ Authentication
- ๐ค Registration & Account
- ๐ฅ Vendor Management
- ๐ข Organization Management
- ๐จโ๐ผ Admin Management
- ๐ Role Management
- Quick Endpoint Index
- ๐ง Vendor Invitations
- ๐ฅ Add Organization Team Member
- ๐ Notifications
- ๐ File Management
- ๐ณ Plan Management
- ๐งพ Invoice Management
- ๐จ Error Responses
- ๐ง Testing Workflow
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/apiReplace 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:
{
"status": 200,
"success": true,
"message": "Human-readable message",
"data": {}
}Paginated lists add:
{
"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):
{
"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 ยท PublicRequest body:
{
"email": "admin@mydesk.com",
"password": "Admin@123456"
}Success (200):
{
"status": "200",
"message": "Login successful",
"data": {
"token": "plaintext_admin_token_use_as_bearer"
}
}cURL:
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 ยท PublicRequest body:
{
"token": "plaintext_admin_token"
}Success (200):
{
"status": "200",
"message": "Token is valid",
"data": {
"name": "Admin",
"email": "admin@mydesk.com",
"role_id": 1,
"status": 1
}
}---
3. Vendor Login
POST
/login ยท PublicRequest body (password):
{
"email": "vendor@example.com",
"action": "password",
"password": "SecurePass1!"
}Request body (OTP request):
{
"email": "vendor@example.com",
"action": "otp"
}Success โ password login with organizations (200):
{
"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):
{
"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):
{
"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 ยท PublicRequest body:
{
"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 ยท PublicRequest body:
{
"api_token": "vendor_core_api_token"
}Success (200):
{
"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 ยท PublicAlternative login that issues core + workspace session tokens (no OTP).
Request body:
{
"email": "vendor@example.com",
"password": "SecurePass1!"
}Success (200):
{
"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 ยท PublicRequest body:
{
"email": "newvendor@example.com",
"password": "SecurePass1!",
"password_confirmation": "SecurePass1!"
}Password rules: min 8, mixed case, numbers, symbols, uncompromised.
Success (200):
{
"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 ยท PublicRequest body:
{
"email": "vendor@example.com"
}Success (200):
{
"status": 200,
"success": true,
"message": "Password reset link sent successfully to your email..."
}---
9. Reset Password
POST
/reset-password ยท PublicRequest body:
{
"token": "reset_token_from_email",
"password": "NewSecurePass1!",
"password_confirmation": "NewSecurePass1!"
}Success (200):
{
"status": 200,
"success": true,
"message": "Password reset successfully!"
}---
10. Verify Email
GET
/verify-email?token={verification_token} ยท PublicSuccess (200):
{
"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:
{
"email": "vendor@example.com"
}Success (200):
{
"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 ยท ProtectedQuery 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=janeSuccess (200):
{
"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} ยท ProtectedSuccess (200):
{
"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} ยท ProtectedRequest body:
{
"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} ยท ProtectedSoft-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):
{
"status": 200,
"success": true,
"message": "Vendor activated successfully!"
}---
Organization Management (Protected)
17. Create Organization
POST
/organization ยท ProtectedProvisions a new tenant database immediately.
Request body:
{
"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):
{
"status": 200,
"success": true,
"message": "Success",
"data": {
"orgcode": "1588571752",
"name": "Acme Corp"
}
}cURL:
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 ยท ProtectedQuery:
items_per_page, organization_id, filter_status, search, sort, orderAdmin response โ paginated with
vendor_name, deleted_at, etc.Vendor response โ unpaginated list scoped to owned/member orgs:
{
"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} ยท ProtectedSuccess (200):
{
"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} ยท ProtectedRequest body:
{
"organization_name": "Acme Corporation",
"full_name": "Jane Doe",
"is_active": true,
"layout_preference": "list"
}---
21. Delete Organization
DELETE
/organization/{orgcode} ยท ProtectedPermanently deletes organization and tenant database.
---
22. Organization Quick Actions
POST
/organization/action/{orgcode} ยท ProtectedRequest body:
{
"action": "soft_delete"
}Allowed:
soft_delete, activate, deactivate---
23. Send Organization Invitations
POST
/send-organization-invitations ยท ProtectedRequest body:
{
"emails": "user1@example.com, user2@example.com",
"organization_id": 1
}Success (200):
{
"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 when the teammate should become a vendor linked to the org.---
Add Organization Team Member
POST
/organization/team-members ยท ProtectedCreate (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:
{
"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):
{
"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:
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). - 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
VendorInvitationMailusingFRONTEND_URLfor 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 ยท ProtectedRequest body:
{
"orgcode": "1588571752"
}Success (200):
{
"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 ยท ProtectedSuccess (200):
{
"status": 200,
"success": true,
"message": "Organizations listed successfully",
"data": [
{ "id": 1, "name": "Acme Corp", "vendor_id": 1 }
]
}---
27. Organization Stats
POST
/org-stats ยท PublicRequest body:
{
"orgcode": "1588571752"
}Success (200):
{
"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):
{
"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 ยท ProtectedContent-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 ยท ProtectedSuccess (200):
{
"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 ยท ProtectedRequest body:
{
"file_path": "organizations/1588571752/documents/report.pdf"
}---
Admin Management (Protected)
31. List Admins
GET
/admins ยท ProtectedQuery:
items_per_page, filter_role, filter_status, search, sort, orderSuccess (200):
{
"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 ยท ProtectedRequest body:
{
"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} ยท ProtectedRequest body:
name, email, avatar, password (min 6), role_id, status โ all optional---
35. Delete Admin
DELETE
/admin/{id}?action=soft_delete ยท ProtectedQuery
action: soft_delete, activate, deactivate, permanent_delete---
36. Send Admin Reset Link
POST
/admin/send-reset-link ยท ProtectedRequest body:
{
"email": "admin@mydesk.com"
}---
37. Reset Admin Password
POST
/admin/password/reset/{token} ยท PublicRequest body:
{
"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 ยท ProtectedRequest body:
{
"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 ยท ProtectedRequest body:
{
"role_id": 1
}---
Vendor Invitations (Public)
For the authenticated frontend flow (logged-in vendor adds a teammate by email), use 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 ยท PublicCreates 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:
{
"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):
{
"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 ยท PublicRequest body:
{
"invitation_token": "uuid-from-invitation-url",
"orgcode": "1588571752",
"password": "ChooseASecurePass1!",
"password_confirmation": "ChooseASecurePass1!"
}Success (200):
{
"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:
vendors.password and synced to the org user.password.---
Notifications (Public)
46. Send Notification
POST
/notify ยท PublicBroadcasts via Laravel Reverb to organization users.
Request body:
{
"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):
{
"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 ยท ProtectedQuery:
items_per_page, search, active_only (admin only)Vendors see active plans only. Admins see all unless
active_only=true.Success (200):
{
"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 ยท AdminRequest body:
{
"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 ยท AdminFails with
400 if vendors are still assigned.---
52. Assign Plan to Vendor (Admin only)
POST
/vendor/{vendorId}/assign-plan ยท Protected ยท AdminRequest body:
{
"plan_id": 2,
"billing_day": 15
}billing_day: 1โ28 (day of month for recurring invoices).Success (200):
{
"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 ยท ProtectedVendor can only access own plan. Admin can access any vendor.
---
Invoice Management (Protected)
Invoice statuses:
draft, pending, paid, overdue, cancelled, void Generation types:
automatic, manual54. List Invoices
GET
/invoices ยท ProtectedQuery:
| 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} ยท ProtectedSuccess (200):
{
"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 ยท AdminRequest body:
{
"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 ยท AdminSingle vendor:
{
"vendor_id": 1,
"generation_type": "manual",
"period_start": "2026-07-01",
"period_end": "2026-07-31"
}All vendors (bulk monthly):
{
"all_vendors": true,
"period_start": "2026-07-01"
}Bulk success (200):
{
"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:
php artisan invoices:generate --month=2026-07 --mark-overdue---
58. Update Invoice (Admin only)
PUT
/invoice/{id} ยท Protected ยท AdminRequest body:
{
"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 ยท AdminRequest body:
{
"paid_at": "2026-07-20T14:30:00Z"
}---
60. Cancel Invoice (Admin only)
POST
/invoice/{id}/cancel ยท Protected ยท AdminRequest body (optional):
{
"notes": "Customer requested cancellation"
}---
61. Void Invoice (Admin only)
POST
/invoice/{id}/void ยท Protected ยท Admin---
62. Delete Invoice (Admin only)
DELETE
/invoice/{id} ยท Protected ยท AdminSoft-delete. Paid invoices cannot be deleted โ void instead.
---
63. List Vendor Invoices
GET
/vendor/{vendorId}/invoices ยท ProtectedSame 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
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
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).---