SS API Docs
Documentation Frontend API Reference
Admin API

Frontend API Reference

Complete reference for frontend developers — authentication, endpoints, models, enums, and integration patterns.

REST + JWT JSON responses Role-based access

Shipping System — Frontend API Reference

Detailed reference for frontend developers integrating with the Admin API.

Scope: The Admin API (/api/admin/*) covers authentication, admin/role/permission management, drivers, vehicles, driver unavailabilities, driver–vehicle assignments, users, shipments, shipment stops, proof of deliveries, failed deliveries, routes, and route stops.


Table of Contents

  1. Quick Start
  2. Global Conventions
  3. Authentication
  4. Profile
  5. Admins
  6. Roles & Permissions
  7. Drivers
  8. Vehicles
  9. Driver Unavailabilities
  10. Driver–Vehicle Assignments
  11. Users (Customers)
  12. Shipments
  13. Shipment Stops
  14. Proof of Deliveries
  15. Failed Deliveries
  16. Routes
  17. Route Stops
  18. Domain Models Reference
  19. Enums Reference
  20. Permissions Reference
  21. Error Handling
  22. Frontend Integration Checklist

Quick Start

HTTP
POST /api/admin/login
Content-Type: application/json
Accept-Language: en

{
  "email": "mousa@example.com",
  "password": "password"
}

Store the returned JWT and send it on every subsequent request:

HTTP
GET /api/admin/drivers
Authorization: Bearer {token}
Accept: application/json
Accept-Language: en

Base URL: {APP_URL}/api — e.g. http://localhost:8000/api when running php artisan serve.


Global Conventions

Request Headers

Header Required Description
Authorization Yes (except login) Bearer {jwt_token}
Accept Recommended application/json
Accept-Language Optional ar (default) or en — controls translated message text
Content-Type Varies application/json for JSON bodies; multipart/form-data when uploading images

Response Envelope

Every API response uses this structure:

JSON
{
    "status": "Success",
    "message": "Data fetched successfully",
    "data": {},
    "statusCode": 200
}
Field Type Description
status "Success" | "Error" Outcome indicator
message string Human-readable, localized message
data object | array | null Payload (null on delete/logout)
statusCode integer HTTP status code echoed in body

On login success, the JWT is also returned in the Authorization response header as Bearer {token}.

HTTP Methods

Updates use POST, not PUT or PATCH.

All update endpoints follow the pattern POST /api/admin/{resource}/{id}.

Content Types

Scenario Content-Type
JSON CRUD (no file) application/json
Create/update with image (admin, driver, profile) multipart/form-data
Assign/unassign vehicle No body required

When using multipart/form-data:

  • Send scalar fields as form fields.
  • Send working_days as repeated fields or JSON string (array).
  • Send roles / permissions as repeated fields or JSON array string.
  • Send image as a file field (max 5 MB, must be an image).

Listing, Filtering & Sorting

Most GET list endpoints accept query string parameters:

Parameter Type Description
search string Full-text search across model-specific columns
sort_by string Column name (must be in model's sortable list)
sort_direction "asc" | "desc" Sort direction (default varies by model)
page integer Page number (paginated endpoints only)

Query string values "null" and "" are automatically converted to null by middleware.

Pagination

Paginated endpoints (GET .../paginated) wrap results like this:

JSON
{
    "drivers": [],
    "total": 50,
    "count": 10,
    "per_page": 10,
    "current_page": 1,
    "total_pages": 5,
    "links": {
        "first": "http://localhost:8000/api/admin/drivers/paginated?page=1",
        "last": "http://localhost:8000/api/admin/drivers/paginated?page=5",
        "prev": null,
        "next": "http://localhost:8000/api/admin/drivers/paginated?page=2"
    }
}

The collection key matches the resource name:

Endpoint prefix Collection key
/admins/paginated admins
/roles/paginated roles
/drivers/paginated drivers
/vehicles/paginated vehicles
/users/paginated users
/driver-unavailabilities/paginated driver_unavailabilities
/shipments/paginated shipments
/shipment-stops/paginated shipment_stops
/routes/paginated routes

Page size is fixed at 10 per page.


Authentication

All routes under /api/admin/* except POST /login require a valid JWT in the Authorization header.

  • Guard: admin
  • Library: JWT Auth (tymon/jwt-auth)
  • Token TTL: Returned as expires_in (seconds) in login response. Configured via JWT_TTL env (minutes); if unset, token may not expire.

POST /api/admin/login

Authenticate an admin and receive a JWT.

Auth required: No

Request body:

Field Type Required Validation
email string Yes Valid email
password string Yes Non-empty string
fcm_token string No Push notification device token; stored on admin record

Example request body:

JSON
{
    "email": "mousa@example.com",
    "password": "SecurePass123!",
    "fcm_token": "device-fcm-token-abc123"
}

Success response 200:

JSON
{
    "status": "Success",
    "message": "User successfully signed in",
    "data": {
        "admin": {
            "id": 1,
            "name": "mousa",
            "email": "mousa@example.com",
            "roles": ["operations_manager"],
            "permission_groups": [
                {
                    "group": "Driver",
                    "group_label": "Drivers",
                    "permissions": [
                        {
                            "id": 1,
                            "name": "ViewAny:Driver",
                            "display_name": "View Any",
                            "group": "Driver"
                        }
                    ]
                }
            ]
        },
        "token": "eyJ0eXAiOiJKV1QiLCJhbGc...",
        "token_type": "bearer",
        "expires_in": 3600
    },
    "statusCode": 200
}

Error responses:

Status When
401 Invalid email/password (credentialsError)
422 Validation failed (missing email/password)
500 Token generation failed (couldNotCreateToken)

POST /api/admin/logout

Invalidate the current JWT session.

Auth required: Yes

Request body: None

Success response 200:

JSON
{
    "status": "Success",
    "message": "User successfully signed out",
    "data": null,
    "statusCode": 200
}

Error responses:

Status When
403 No token provided (Unauthenticated)
500 Logout failed (couldNotLogout)

Profile

Manage the currently authenticated admin's own profile. Does not require admin-management permissions.


GET /api/admin/profile

Permission required: None (authentication only)

Success response 200: Returns an AdminResource (see Admins) with:

  • is_current_admin: true
  • permission_groups included
  • roles included

POST /api/admin/profile

Update the authenticated admin's profile.

Permission required: None (authentication only)

Request body (JSON or multipart):

Field Type Required Validation
name string No Max 255 chars
email string No Valid email, unique among admins
password string No Laravel password defaults
image file No Image, max 5 MB

Example request body:

JSON
{
    "name": "mousa",
    "email": "mousa.updated@example.com",
    "password": "NewSecurePass123!"
}

Success response 200: Updated AdminResource with message profileUpdatedSuccessfully.


Dashboard Stats

Aggregated KPI statistics for the admin operations dashboard. Both endpoints require the View:Dashboard permission (or super-admin).


GET /api/admin/dashboard/overview

High-level KPI cards for today, this week, and this month. No query parameters.

Permission required: View:Dashboard

Success response 200:

JSON
{
    "status": "Success",
    "message": "...",
    "data": {
        "shipments_created_today": 12,
        "shipments_created_this_week": 48,
        "shipments_created_this_month": 190,
        "active_routes": 5,
        "active_drivers": 18,
        "pending_pickups": 9,
        "out_for_delivery": 14,
        "delivered_today": 22
    },
    "statusCode": 200
}
Field Description
shipments_created_today Shipments created on the current date
shipments_created_this_week Shipments created since Monday of the current week
shipments_created_this_month Shipments created since the 1st of the current month
active_routes Routes with status active
active_drivers Drivers with is_active = true
pending_pickups Shipments in pending or pickup_in_progress
out_for_delivery Shipments in out_for_delivery
delivered_today Shipments delivered today (via POD delivered_at, fallback updated_at)

GET /api/admin/dashboard/trends

Daily time-series for charts, spanning 7 or 30 days.

Permission required: View:Dashboard

Query parameters:

Parameter Type Required Description
days integer 7 or 30 No Number of days to include (defaults to 30)

Success response 200:

JSON
{
    "data": {
        "days": 30,
        "from": "2026-06-20",
        "to": "2026-07-19",
        "series": [
            {
                "date": "2026-06-20",
                "shipments_created": 8,
                "shipments_delivered": 6,
                "failed_deliveries": 1,
                "failure_rate": 0.1429
            }
        ]
    }
}
  • Every calendar day in the range is present (zeros when no activity).
  • failure_rate = failed_deliveries / (shipments_delivered + failed_deliveries), or 0 when both are 0.
  • shipments_delivered uses POD delivered_at when available, falling back to the shipment's updated_at.

Validation error 422: Returned when days is not 7 or 30.


Admins

Back-office user management.

Permission prefix: Admin (e.g. ViewAny:Admin, Create:Admin)

Hidden admin accounts (configured via HIDDEN_ADMIN env) are excluded from listings.

Endpoints

Method Path Description Permission
GET /api/admin/admins List all admins ViewAny:Admin
GET /api/admin/admins/paginated List paginated ViewAny:Admin
GET /api/admin/admins/{id} Get single admin View:Admin
POST /api/admin/admins Create admin Create:Admin
POST /api/admin/admins/{id} Update admin Update:Admin
DELETE /api/admin/admins/{id} Delete admin Delete:Admin

List Query Parameters

Parameter Description
search Searches name, email
role Filter by role name (exact match)
sort_by name, email, created_at
sort_direction asc or desc (default: asc by name)

Create Request Body

Field Type Required Validation
name string Yes Max 255
email string Yes Valid email, unique
password string Yes Laravel password defaults
image file No Image, max 5 MB
roles string[] No Array of existing role names

Example request body:

JSON
{
    "name": "mousa",
    "email": "mousa@example.com",
    "password": "SecurePass123!",
    "roles": ["operations_manager"]
}

Update Request Body

Same fields as create, all optional (sometimes rules apply). Omit password to keep current password. Send roles: [] to remove all roles.

Example request body:

JSON
{
    "name": "mousa",
    "email": "mousa.admin@example.com",
    "roles": ["super_admin"]
}

Admin Resource Shape

JSON
{
    "id": 1,
    "name": "mousa",
    "email": "mousa@example.com",
    "image": "http://localhost:8000/storage/1/admin_image.jpg",
    "roles": ["super_admin"],
    "permission_groups": [],
    "is_current_admin": false,
    "created_at": "2026-01-15T10:00:00.000000Z",
    "updated_at": "2026-01-15T10:00:00.000000Z"
}
Field Notes
image Full URL to profile image, or empty string if none
roles Array of role name strings
permission_groups Only included on GET /profile and GET /admins/{id}
is_current_admin true if this admin is the one making the request

Delete Constraints

  • Cannot delete your own account → 403 with message cannotDeleteCurrentAdmin

Roles & Permissions

Role-based access control using Spatie Permission (guard: admin).

Permission prefixes: Role, Permission

Endpoints

Method Path Description Permission
GET /api/admin/permissions All permissions grouped ViewAny:Permission
GET /api/admin/roles List all roles ViewAny:Role
GET /api/admin/roles/paginated List paginated ViewAny:Role
GET /api/admin/roles/{id} Get single role View:Role
POST /api/admin/roles Create role Create:Role
POST /api/admin/roles/{id} Update role Update:Role
POST /api/admin/roles/{id}/permissions Sync permissions only Update:Role
DELETE /api/admin/roles/{id} Delete role Delete:Role

List Query Parameters (roles)

Parameter Description
search Searches role name
sort_by name, created_at
sort_direction asc or desc (default: asc by name)

Create Role Request Body

Field Type Required Validation
name string Yes Unique per guard, max 255
permissions string[] No Array of permission name strings

Example request body:

JSON
{
    "name": "warehouse_manager",
    "permissions": ["ViewAny:Driver", "Create:Driver", "ViewAny:Vehicle"]
}

Update Role Request Body

Field Type Required Validation
name string No Unique per guard, max 255
permissions string[] No Replaces all permissions when provided

Example request body:

JSON
{
    "name": "warehouse_manager",
    "permissions": ["ViewAny:Driver", "Update:Driver"]
}

Sync Permissions Request Body

POST /api/admin/roles/{id}/permissions

Field Type Required Validation
permissions string[] Yes Array of permission name strings

Example request body:

JSON
{
    "permissions": [
        "ViewAny:Driver",
        "Create:Driver",
        "ViewAny:Vehicle",
        "Create:Vehicle"
    ]
}

Success message: permissionsAssignedSuccessfully

Role Resource Shape

JSON
{
    "id": 2,
    "name": "warehouse_manager",
    "guard_name": "admin",
    "permission_groups": [
        {
            "group": "Driver",
            "group_label": "Drivers",
            "permissions": [
                {
                    "id": 1,
                    "name": "ViewAny:Driver",
                    "display_name": "View Any",
                    "group": "Driver"
                }
            ]
        }
    ],
    "created_at": "2026-01-15T10:00:00.000000Z",
    "updated_at": "2026-01-15T10:00:00.000000Z"
}

Permission Group Shape

Used in login, profile, roles, and the permissions list:

JSON
{
    "group": "Driver",
    "group_label": "Drivers",
    "permissions": [
        {
            "id": 1,
            "name": "ViewAny:Driver",
            "display_name": "View Any",
            "group": "Driver"
        }
    ]
}

Constraints

  • Super admin role cannot be modified or deleted → 403 (cannotModifySuperAdmin)

Drivers

Delivery driver management.

Permission prefix: Driver

Endpoints

Method Path Description Permission
GET /api/admin/drivers List all drivers ViewAny:Driver
GET /api/admin/drivers/paginated List paginated ViewAny:Driver
GET /api/admin/drivers/{id} Get single driver View:Driver
POST /api/admin/drivers Create driver Create:Driver
POST /api/admin/drivers/{id} Update driver Update:Driver
DELETE /api/admin/drivers/{id} Delete driver Delete:Driver
GET /api/admin/drivers/{id}/vehicle-assignments Vehicle assignments ViewAny:DriverVehicleAssignment
GET /api/admin/drivers/{id}/unavailabilities Unavailability periods ViewAny:DriverUnavailability
POST /api/admin/drivers/{driverId}/vehicles/{vehicleId}/assign Assign vehicle Create:DriverVehicleAssignment
POST /api/admin/drivers/{driverId}/vehicles/{vehicleId}/unassign Unassign vehicle Update:DriverVehicleAssignment

List Query Parameters

Parameter Type Description
search string Searches name, phone, email, driver_number, license_number
is_active boolean Filter by active status
lat float Latitude for nearby search (requires lng)
lng float Longitude for nearby search (requires lat)
radius float Search radius in km (default: 10)
work_start_time time Filter drivers whose shift overlaps this start time
work_end_time time Filter drivers whose shift overlaps this end time
work_start_time_from time Minimum shift start time
work_start_time_to time Maximum shift start time
work_end_time_from time Minimum shift end time
work_end_time_to time Maximum shift end time
sort_by string See sortable columns below
sort_direction string asc or desc

Sortable columns: name, phone, driver_number, work_start_time, work_end_time, created_at, distance

When lat/lng are provided, results include a distance field (km) and default-sort by nearest first unless sort_by is explicitly set.

Time format: HH:MM:SS or HH:MM (auto-normalized to HH:MM:SS).

Create Request Body

Field Type Required Validation
name string Yes Max 255
phone string Yes Max 50
phone_country string Yes Max 10 (e.g. +963)
email string No Valid email, unique
password string Yes Laravel password defaults
address string Yes
license_number string No Max 255
working_days string[] Yes Min 1 item; see Working Days
work_start_time time Yes Must differ from work_end_time
work_end_time time Yes Must differ from work_start_time
lat float No -90 to 90
lng float No -180 to 180
fcm_token string No Push notification token
is_active boolean No Default: true
image file No Image, max 5 MB
vehicle_id integer No Must exist in vehicles; assigns vehicle on create when provided
driver_number string No Unique; auto-generated 6-digit number if omitted

Example request body:

JSON
{
    "name": "mousa",
    "phone": "944123456",
    "phone_country": "+963",
    "email": "mousa.driver@example.com",
    "password": "SecurePass123!",
    "address": "Damascus, Syria",
    "license_number": "SY-12345",
    "working_days": ["sun", "mon", "tue", "wed", "thu"],
    "work_start_time": "08:00:00",
    "work_end_time": "17:00:00",
    "lat": 33.5138,
    "lng": 36.2765,
    "is_active": true,
    "vehicle_id": 7
}

Vehicle assignment on update: send vehicle_id to assign or switch vehicles; send vehicle_id: null to release the current assignment. Omit the field to leave the assignment unchanged.

Update Request Body

Same fields as create. All fields optional except where sometimes + required applies. Password optional (omit to keep current).

Example request body:

JSON
{
    "name": "mousa",
    "phone": "944654321",
    "phone_country": "+963",
    "work_start_time": "09:00:00",
    "work_end_time": "18:00:00",
    "is_active": true
}

Driver Resource Shape

JSON
{
    "id": 1,
    "driver_number": "482910",
    "name": "mousa",
    "phone": "944123456",
    "phone_country": "+963",
    "email": "mousa.driver@example.com",
    "image": "http://localhost:8000/storage/2/driver_image.jpg",
    "address": "Damascus, Syria",
    "license_number": "DL-12345",
    "working_days": ["sun", "mon", "tue", "wed", "thu"],
    "work_start_time": "08:00:00",
    "work_end_time": "17:00:00",
    "lat": 24.7136,
    "lng": 46.6753,
    "is_active": true,
    "created_at": "2026-01-15T10:00:00.000000Z",
    "updated_at": "2026-01-15T10:00:00.000000Z",
    "distance": 3.2,
    "vehicle": {
        "id": 7,
        "plate_number": "ABC-1234",
        "owner_type": "driver",
        "owner_type_label": "سائق",
        "owner_id": 1,
        "max_weight": 500,
        "max_volume": 10,
        "is_active": true,
        "created_at": "2026-01-15T10:00:00.000000Z",
        "updated_at": "2026-01-15T10:00:00.000000Z"
    }
}
Field Notes
distance Only present when nearby filter (lat/lng) is used
working_days JSON array of day codes
image Full URL or empty string
vehicle Nested VehicleResource for the driver's current active assignment (released_at is null); null when no vehicle is assigned

Vehicles

Fleet vehicle management.

Permission prefix: Vehicle

Endpoints

Method Path Description Permission
GET /api/admin/vehicles List all vehicles ViewAny:Vehicle
GET /api/admin/vehicles/paginated List paginated ViewAny:Vehicle
GET /api/admin/vehicles/{id} Get single vehicle View:Vehicle
POST /api/admin/vehicles Create vehicle Create:Vehicle
POST /api/admin/vehicles/{id} Update vehicle Update:Vehicle
DELETE /api/admin/vehicles/{id} Delete vehicle Delete:Vehicle

List Query Parameters

Parameter Description
search Searches plate_number
owner_type Exact match: company or driver
is_active Boolean filter
sort_by plate_number, owner_type, max_weight, max_volume, created_at
sort_direction asc or desc (default: asc by plate_number)

Create Request Body

Field Type Required Validation
plate_number string Yes Unique, max 255
owner_type string Yes company or driver — see OwnerTypes
owner_id integer Conditional Required when owner_type is driver; must be a valid driver ID. Must not be sent when owner_type is company
max_weight number Yes Min 0 (kg)
max_volume number Yes Min 0 (m³)

Example request body:

JSON
{
    "plate_number": "DMS-1234",
    "owner_type": "company",
    "max_weight": 1500,
    "max_volume": 12.5
}

Example request body (driver-owned):

JSON
{
    "plate_number": "DMS-5678",
    "owner_type": "driver",
    "owner_id": 1,
    "max_weight": 2000,
    "max_volume": 15
}

Update Request Body

Field Type Required Validation
plate_number string No Unique, max 255
owner_type string No company or driver
owner_id integer Conditional Same rules as create
max_weight number No Min 0
max_volume number No Min 0
is_active boolean No

Example request body:

JSON
{
    "plate_number": "DMS-9999",
    "max_weight": 1800,
    "max_volume": 14,
    "is_active": false
}

Vehicle Resource Shape

JSON
{
    "id": 1,
    "plate_number": "ABC-1234",
    "owner_type": "company",
    "owner_type_label": "الشركة",
    "owner_id": null,
    "max_weight": 1500,
    "max_volume": 12.5,
    "is_active": true,
    "created_at": "2026-01-15T10:00:00.000000Z",
    "updated_at": "2026-01-15T10:00:00.000000Z"
}
Field Notes
owner_type_label Localized Arabic label (API always returns Arabic label regardless of Accept-Language)
owner_id null for company-owned vehicles; driver ID for driver-owned

Driver Unavailabilities

Time-off / unavailability blocks for drivers.

Permission prefix: DriverUnavailability

Endpoints

Method Path Description Permission
GET /api/admin/driver-unavailabilities List all ViewAny:DriverUnavailability
GET /api/admin/driver-unavailabilities/paginated List paginated ViewAny:DriverUnavailability
GET /api/admin/driver-unavailabilities/{id} Get single View:DriverUnavailability
POST /api/admin/driver-unavailabilities Create Create:DriverUnavailability
POST /api/admin/driver-unavailabilities/{id} Update Update:DriverUnavailability
DELETE /api/admin/driver-unavailabilities/{id} Delete Delete:DriverUnavailability

Also accessible via GET /api/admin/drivers/{id}/unavailabilities.

List Query Parameters

Parameter Description
driver_id Filter by driver ID (exact)
sort_by start_date, end_date, created_at
sort_direction Default: desc by start_date

Create Request Body

Field Type Required Validation
driver_id integer Yes Must exist in drivers table
start_date date Yes ISO date or datetime
end_date date Yes Must be after start_date
reason string No Free text

Example request body:

JSON
{
    "driver_id": 1,
    "start_date": "2026-06-01",
    "end_date": "2026-06-05",
    "reason": "Annual leave"
}

Update Request Body

Same fields, all optional. If both dates are provided on update, end_date must still be after start_date.

Example request body:

JSON
{
    "start_date": "2026-06-01",
    "end_date": "2026-06-10",
    "reason": "Extended leave"
}

Driver Unavailability Resource Shape

JSON
{
    "id": 1,
    "driver_id": 5,
    "start_date": "2026-06-01T00:00:00.000000Z",
    "end_date": "2026-06-05T00:00:00.000000Z",
    "reason": "Annual leave",
    "created_at": "2026-05-20T10:00:00.000000Z",
    "updated_at": "2026-05-20T10:00:00.000000Z"
}

Driver–Vehicle Assignments

Links drivers to vehicles for operational use. A vehicle can only have one active assignment at a time (released_at === null).

Permission prefix: DriverVehicleAssignment

Endpoints

Method Path Description Permission
GET /api/admin/drivers/{id}/vehicle-assignments List assignments for driver ViewAny:DriverVehicleAssignment
POST /api/admin/drivers/{driverId}/vehicles/{vehicleId}/assign Assign vehicle to driver Create:DriverVehicleAssignment
POST /api/admin/drivers/{driverId}/vehicles/{vehicleId}/unassign Release assignment Update:DriverVehicleAssignment

Assign

No request body. Creates a record with:

  • assigned_at = current timestamp
  • released_at = null

Error: 422 if vehicle already has an active assignment (vehicleAlreadyAssigned)

Unassign

No request body. Sets released_at to current timestamp on the active assignment.

Error: 404 if no active assignment exists (vehicleAssignmentNotFound)

Driver Vehicle Assignment Resource Shape

JSON
{
    "id": 1,
    "driver_id": 3,
    "vehicle_id": 7,
    "assigned_at": "2026-06-01T08:00:00.000000Z",
    "released_at": null,
    "vehicle": {
        "id": 7,
        "plate_number": "XYZ-5678",
        "owner_type": "company",
        "owner_type_label": "الشركة",
        "owner_id": null,
        "max_weight": 2000,
        "max_volume": 15,
        "is_active": true,
        "created_at": "...",
        "updated_at": "..."
    },
    "driver": {
        "id": 3,
        "driver_number": "482910",
        "name": "mousa"
    },
    "created_at": "2026-06-01T08:00:00.000000Z",
    "updated_at": "2026-06-01T08:00:00.000000Z"
}
Field Notes
vehicle Nested VehicleResource when relation is loaded
driver Nested DriverResource when relation is loaded
released_at null = currently assigned

Users (Customers)

End customers who place shipments. These are not admin accounts.

Permission prefix: User

Endpoints

Method Path Description Permission
GET /api/admin/users List all users ViewAny:User
GET /api/admin/users/paginated List paginated ViewAny:User
GET /api/admin/users/{id} Get single user View:User
POST /api/admin/users Create user Create:User
POST /api/admin/users/{id} Update user Update:User
DELETE /api/admin/users/{id} Delete user Delete:User

List Query Parameters

Parameter Description
search Searches name, phone, email, company_name
sort_by name, phone, email, company_name, created_at
sort_direction asc or desc (default: asc by name)

Create Request Body

Field Type Required Validation
name string Yes Max 255
phone string Yes Max 50; unique per phone_country
phone_country string Yes Max 10
email string No Valid email, unique
company_name string No

Example request body:

JSON
{
    "name": "mousa",
    "phone": "944123456",
    "phone_country": "+963",
    "email": "mousa@example.com",
    "company_name": "Mousa Trading Co."
}

Update Request Body

Same fields, all optional. Phone uniqueness is re-validated when phone or phone_country changes.

Example request body:

JSON
{
    "name": "mousa",
    "phone": "944654321",
    "phone_country": "+963",
    "company_name": "Mousa Logistics"
}

User Resource Shape

JSON
{
    "id": 1,
    "name": "mousa",
    "phone": "944123456",
    "phone_country": "+963",
    "email": "mousa@example.com",
    "company_name": "Mousa Trading Co.",
    "created_at": "2026-01-15T10:00:00.000000Z",
    "updated_at": "2026-01-15T10:00:00.000000Z"
}

Shipments

Full shipment lifecycle management — create, update, confirm, cancel, assign to route, and bulk operations.

Permission prefix: Shipment

Endpoints

Method Path Description Permission
GET /api/admin/shipments List all shipments ViewAny:Shipment
GET /api/admin/shipments/paginated List paginated ViewAny:Shipment
GET /api/admin/shipments/{id} Get single shipment (id or shipment_number) View:Shipment
POST /api/admin/shipments Create shipment Create:Shipment
POST /api/admin/shipments/create-with-user Upsert user + create shipment Create:Shipment
POST /api/admin/shipments/{id} Update shipment Update:Shipment
DELETE /api/admin/shipments/{id} Delete draft shipment Delete:Shipment
POST /api/admin/shipments/{id}/confirm Confirm draft → pending Update:Shipment
POST /api/admin/shipments/{id}/cancel Cancel shipment Update:Shipment
POST /api/admin/shipments/{id}/status Admin status override Update:Shipment
POST /api/admin/shipments/{id}/assign-route Assign to a route Update:Shipment
POST /api/admin/shipments/{id}/unassign-route Remove from route Update:Shipment
GET /api/admin/shipments/{id}/events List shipment events View:Shipment
POST /api/admin/shipments/bulk/cancel Bulk cancel Create:Shipment
POST /api/admin/shipments/bulk/confirm Bulk confirm Create:Shipment
POST /api/admin/shipments/bulk/assign-route Bulk assign route Create:Shipment

List Query Parameters

Parameter Type Description
search string Searches shipment_number, user_name, notes
status string Exact match — see ShipmentStatuses
type string Exact match — see ShipmentTypes
user_id integer Filter by customer
payment_type string Exact match — see PaymentTypes
priority string Exact match — see ShipmentPriorities
delivery_date_from date Filter delivery date range start
delivery_date_to date Filter delivery date range end
sort_by string shipment_number, delivery_date, status, created_at
sort_direction string asc or desc (default: desc by created_at)

Get Single Shipment

GET /api/admin/shipments/{id}

{id} accepts either the numeric primary key or the shipment_number (e.g. SH-AB12CD34).

Create Request Body

Field Type Required Validation
user_id integer Yes Must exist in users
delivery_date date Yes Must be after today
payment_type string Yes prepaid or cod
cod_amount number Conditional Required when payment_type = cod; min 0
notes string No Optional
priority string No low, normal, high, or urgent — see ShipmentPriorities; defaults to normal
stops array Yes Min 2 items; exactly 1 pickup, exactly 1 delivery
stops.*.type string Yes pickup, delivery, or stop
stops.*.country string Yes
stops.*.city string Yes
stops.*.lat number Yes -90 to 90
stops.*.lng number Yes -180 to 180
stops.*.sequence integer Yes Min 1
stops.*.contact_name string No
stops.*.phone string No
stops.*.phone_country string No
stops.*.area string No
stops.*.street string No
stops.*.building string No
stops.*.floor string No
stops.*.apartment string No
stops.*.full_address string No
items array Yes Min 1 item
items.*.name string Yes
items.*.quantity integer Yes Min 1
items.*.weight number No kg
items.*.length number No cm
items.*.width number No cm
items.*.height number No cm
items.*.description string No
items.*.declared_value number No

Example request body:

JSON
{
    "user_id": 1,
    "delivery_date": "2026-07-15",
    "payment_type": "cod",
    "cod_amount": 150,
    "notes": "Handle with care",
    "priority": "high",
    "stops": [
        {
            "type": "pickup",
            "country": "SA",
            "city": "Riyadh",
            "lat": 24.7136,
            "lng": 46.6753,
            "sequence": 1,
            "contact_name": "Ali Hassan",
            "phone": "0501234567"
        },
        {
            "type": "delivery",
            "country": "SA",
            "city": "Jeddah",
            "lat": 21.4858,
            "lng": 39.1925,
            "sequence": 2,
            "contact_name": "Omar Ahmed",
            "phone": "0559876543"
        }
    ],
    "items": [
        {
            "name": "Electronics Box",
            "quantity": 1,
            "weight": 2.5,
            "declared_value": 300
        }
    ]
}

Create With User (Upsert) Request Body

POST /api/admin/shipments/create-with-user

Same shipment fields as create, but replace user_id with a nested user object. Matches an existing customer by phone + phone_country and updates name / email / company_name, or creates a new user when no match exists. Then creates a draft shipment for that user.

Field Type Required Validation
user object Yes Nested customer payload
user.name string Yes Max 255
user.phone string Yes Valid phone for user.phone_country
user.phone_country string Yes Max 10 (e.g. SA)
user.email string No Unique email (ignored for the matched user on upsert)
user.company_name string No
(shipment fields) Same as Create Request Body except user_id

Example request body:

JSON
{
    "user": {
        "name": "Ahmed Al-Farsi",
        "phone": "0501234567",
        "phone_country": "SA",
        "email": "ahmed@example.com",
        "company_name": "Acme"
    },
    "delivery_date": "2026-07-15",
    "payment_type": "cod",
    "cod_amount": 150,
    "total_weight": 2.5,
    "total_volume": 0.0005,
    "notes": "Handle with care",
    "priority": "high",
    "stops": [
        {
            "type": "pickup",
            "country": "SA",
            "city": "Riyadh",
            "lat": 24.7136,
            "lng": 46.6753
        },
        {
            "type": "delivery",
            "country": "SA",
            "city": "Jeddah",
            "lat": 21.4858,
            "lng": 39.1925
        }
    ],
    "items": [
        {
            "name": "Electronics Box",
            "quantity": 1,
            "weight": 2.5
        }
    ]
}

Update Request Body

Same fields as create, all optional (sometimes rules). Only allowed when status is draft or pending. If stops is included, all stops are replaced.

Confirm Request

POST /api/admin/shipments/{id}/confirm

No request body. Moves status draft → pending.

Error: 422 if shipment is not draft (cannotConfirmShipment)

Cancel Request

POST /api/admin/shipments/{id}/cancel

Field Type Required Validation
reason string No Max 500 chars

Error: 422 if shipment is already in a terminal status (shipmentAlreadyCancelled)

Change Status Request

POST /api/admin/shipments/{id}/status

Field Type Required Validation
status string Yes Any valid ShipmentStatuses value

Admin override — bypasses the normal state machine transitions.

Assign Route Request

POST /api/admin/shipments/{id}/assign-route

Field Type Required Validation
route_id integer Yes Must exist in routes

Shipment must be pending. Creates RouteStop records for each stop and sets status to assigned.

Error: 422 if shipment is not pending (shipmentNotPending)

Unassign Route Request

POST /api/admin/shipments/{id}/unassign-route

No request body. Deletes associated RouteStop rows and reverts status to pending.

Error: 422 if shipment is not assigned (shipmentNotAssigned)

Bulk Operations

Bulk Cancel: POST /api/admin/shipments/bulk/cancel

Field Type Required
ids integer[] Yes
reason string No

Bulk Confirm: POST /api/admin/shipments/bulk/confirm

Field Type Required
ids integer[] Yes

Bulk Assign Route: POST /api/admin/shipments/bulk/assign-route

Field Type Required
ids integer[] Yes
route_id integer Yes

Delete Constraint

  • Only draft shipments can be deleted → 422 (cannotDeleteShipment)

Shipment Resource Shape

JSON
{
  "id": 1,
  "shipment_number": "AB3XZ7QR",
  "user_id": 5,
  "user_name": "Ahmed Al-Farsi",
  "user": { "id": 5, "name": "Ahmed Al-Farsi" },
  "delivery_date": "2026-07-15T00:00:00.000000Z",
  "status": "pending",
  "status_label": "في الانتظار",
  "type": "domestic",
  "type_label": "محلي",
  "payment_type": "cod",
  "payment_type_label": "عند الاستلام",
  "cod_amount": 150,
  "total_weight": 2.5,
  "total_volume": 0.0005,
  "priority": "normal",
  "priority_label": "عادية",
  "priority_color": "primary",
  "notes": "Handle with care",
  "stops": [...],
  "items": [...],
  "events": [...],
  "created_at": "2026-06-21T10:00:00.000000Z",
  "updated_at": "2026-06-21T10:00:00.000000Z"
}

stops, items, and events are only present when the resource is loaded via GET /shipments/{id}.

ShipmentStop Resource Shape

JSON
{
    "id": 1,
    "shipment_id": 1,
    "type": "pickup",
    "type_label": "تحصيل",
    "contact_name": "Ali Hassan",
    "phone": "0501234567",
    "phone_country": null,
    "country": "SA",
    "city": "Riyadh",
    "area": null,
    "street": null,
    "building": null,
    "floor": null,
    "apartment": null,
    "lat": 24.7136,
    "lng": 46.6753,
    "full_address": null,
    "sequence": 1,
    "created_at": "2026-06-21T10:00:00.000000Z",
    "updated_at": "2026-06-21T10:00:00.000000Z"
}

ShipmentItem Resource Shape

JSON
{
    "id": 1,
    "shipment_id": 1,
    "name": "Electronics Box",
    "weight": 2.5,
    "length": null,
    "width": null,
    "height": null,
    "quantity": 1,
    "description": null,
    "declared_value": 300,
    "created_at": "2026-06-21T10:00:00.000000Z",
    "updated_at": "2026-06-21T10:00:00.000000Z"
}

ShipmentEvent Resource Shape

JSON
{
    "id": 1,
    "shipment_id": 1,
    "event_type": "status_changed",
    "event_type_label": "تم تغيير الحالة",
    "data": {
        "from_status": "draft",
        "to_status": "pending"
    },
    "causer_type": "App\\Models\\Admin",
    "causer_id": 1,
    "causer_name": "Admin User",
    "notes": null,
    "created_at": "2026-06-21T10:05:00.000000Z",
    "updated_at": "2026-06-21T10:05:00.000000Z"
}

Shipment Stops

List shipment stops with rich filtering — useful for route planning and operational views. Each stop includes its parent shipment summary.

Permission prefix: ShipmentStop

Endpoints

Method Path Description Permission
GET /api/admin/shipment-stops List all shipment stops ViewAny:ShipmentStop
GET /api/admin/shipment-stops/paginated List paginated ViewAny:ShipmentStop
GET /api/admin/shipment-stops/{id} Get single stop View:ShipmentStop

List Query Parameters

Parameter Type Description
search string Searches contact_name, phone, city, area, street, full_address
type string Stop type — pickup, delivery, or stop
shipment_id integer Filter by shipment
shipment_number string Partial match on parent shipment_number
shipment_status string Exact match on parent shipment status — see ShipmentStatuses
shipment_type string Exact match on parent shipment type — see ShipmentTypes
user_id integer Filter by customer on parent shipment
payment_type string Exact match on parent shipment payment_type
priority string Exact match on parent shipment priority
delivery_date date Parent shipment delivery date (exact day)
delivery_date_from date Parent shipment delivery date range start
delivery_date_to date Parent shipment delivery date range end
country string Exact match on stop country
city string Exact match on stop city
unassigned boolean true = not on an assigned or active route
on_route boolean true = already on an assigned or active route
sort_by string sequence, type, city, created_at, delivery_date
sort_direction string asc or desc (default: desc by created_at)

Shipment Stop Resource Shape

JSON
{
    "id": 12,
    "shipment_id": 4,
    "type": "pickup",
    "type_label": "تحصيل",
    "contact_name": "Warehouse",
    "phone": "0501234567",
    "phone_country": "SA",
    "country": "SA",
    "city": "Riyadh",
    "area": "Al Olaya",
    "street": "King Fahd Rd",
    "building": null,
    "floor": null,
    "apartment": null,
    "lat": 24.7136,
    "lng": 46.6753,
    "full_address": "King Fahd Rd, Al Olaya, Riyadh, SA",
    "sequence": 1,
    "created_at": "2026-06-21T10:00:00.000000Z",
    "updated_at": "2026-06-21T10:00:00.000000Z",
    "shipment": {
        "id": 4,
        "shipment_number": "SH-AB12CD34",
        "delivery_date": "2026-07-15T00:00:00.000000Z",
        "status": "pending",
        "type": "domestic",
        "priority": "normal",
        "user_id": 5,
        "user_name": "Ahmed Al-Farsi",
        "payment_type": "prepaid"
    }
}

Proof of Deliveries

Delivery confirmation records for shipments.

Permission prefix: ProofOfDelivery

Endpoints

Method Path Description Permission
GET /api/admin/shipments/{shipmentId}/proof-of-deliveries List PODs for shipment ViewAny:ProofOfDelivery
POST /api/admin/shipments/{shipmentId}/proof-of-deliveries Create POD Create:ProofOfDelivery
GET /api/admin/proof-of-deliveries/{id} Get single POD View:ProofOfDelivery

Create Request Body

Field Type Required Validation
receiver_name string No
receiver_phone string No
receiver_phone_country string No
delivered_at datetime Yes Valid date
notes string No

Side effects: Sets shipment status to delivered and logs a pod_uploaded event.

Example request body:

JSON
{
    "receiver_name": "Omar Ahmed",
    "receiver_phone": "0559876543",
    "delivered_at": "2026-07-15T14:30:00",
    "notes": "Left at door"
}

ProofOfDelivery Resource Shape

JSON
{
    "id": 1,
    "shipment_id": 1,
    "receiver_name": "Omar Ahmed",
    "receiver_phone": "0559876543",
    "receiver_phone_country": null,
    "delivered_at": "2026-07-15T14:30:00.000000Z",
    "notes": "Left at door",
    "created_by_type": "App\\Models\\Admin",
    "created_by_id": 1,
    "created_by_name": "Admin User",
    "shipment": null,
    "created_at": "2026-07-15T14:31:00.000000Z",
    "updated_at": "2026-07-15T14:31:00.000000Z"
}

shipment is only present when accessed via GET /proof-of-deliveries/{id}.


Failed Deliveries

Records of failed delivery attempts. Created automatically when a RouteStop (of type delivery) is marked failed, or manually via this API.

Permission prefix: FailedDelivery

Endpoints

Method Path Description Permission
GET /api/admin/shipments/{shipmentId}/failed-deliveries List failed deliveries for shipment ViewAny:FailedDelivery
POST /api/admin/shipments/{shipmentId}/failed-deliveries Manually create Create:FailedDelivery
GET /api/admin/failed-deliveries/{id} Get single record View:FailedDelivery

Create Request Body

Field Type Required Validation
reason_code string Yes See FailedDeliveryReasons
notes string No

Side effects: Sets shipment status to delivery_failed (if not already) and logs a delivery_failed event.

Example request body:

JSON
{
    "reason_code": "customer_refused",
    "notes": "Customer refused to accept the package"
}

FailedDelivery Resource Shape

JSON
{
    "id": 1,
    "shipment_id": 1,
    "reason_code": "customer_refused",
    "reason_code_label": "العميل رفض الاستلام",
    "notes": "Customer refused to accept the package",
    "created_by_type": "App\\Models\\Admin",
    "created_by_id": 1,
    "created_by_name": "Admin User",
    "shipment": null,
    "created_at": "2026-07-15T14:35:00.000000Z",
    "updated_at": "2026-07-15T14:35:00.000000Z"
}

shipment is only present when accessed via GET /failed-deliveries/{id}.


Routes

Daily driver routes with ordered stops, distance metrics, and lifecycle actions (start, complete, cancel). Routes can be created manually or generated by the planning job.

Permission prefix: Route

Endpoints

Method Path Description Permission
GET /api/admin/routes List all routes ViewAny:Route
GET /api/admin/routes/paginated List paginated ViewAny:Route
GET /api/admin/routes/{id} Get single route with stops, driver, vehicle View:Route
POST /api/admin/routes Create route with stops Create:Route
POST /api/admin/routes/{id} Update route Update:Route
DELETE /api/admin/routes/{id} Delete draft or assigned route Delete:Route
POST /api/admin/routes/{id}/start Start route (assigned → active) Update:Route
POST /api/admin/routes/{id}/complete Complete route (active → completed) Update:Route
POST /api/admin/routes/{id}/cancel Cancel route Update:Route
GET /api/admin/routes/{id}/stops List ordered route stops View:Route
GET /api/admin/routes/{id}/assignments List driver assignment history for route ViewAny:RouteAssignment
POST /api/admin/routes/plan Dispatch async route planning job Create:Route
GET /api/admin/routes/plan/result Get cached planning summary ViewAny:Route

List Query Parameters

Parameter Type Description
status string Exact match — see RouteStatuses
driver_id integer Filter by driver
vehicle_id integer Filter by vehicle
route_date date Filter by route date
sort_by string route_number, route_date, status, created_at
sort_direction string asc or desc (default: desc by route_date)

Create Request Body

Field Type Required Validation
driver_id integer Yes Must exist in drivers; must have an active vehicle assignment
route_date date Yes
stops array Yes Min 1 item; ordered list of shipment_stop_id values — sequence is assigned automatically
stops.* integer Yes Must exist in shipment_stops; must be unique; must not already be on an active/assigned route; for the same shipment, pickup must appear before delivery

Not accepted on create: vehicle_id, status, total_stops, optimized_distance, duration — these are resolved or calculated by the server.

Server behaviour:

  • Resolves vehicle_id from the driver's latest active DriverVehicleAssignment
  • Sets status to assigned (driver is assigned but has not started)
  • Calculates total_stops, optimized_distance (km), and duration (minutes) from stop coordinates
  • Creates RouteStop records with estimated arrival/departure times
  • Sets linked shipments (pending or delivery_failed) to assigned

Errors:

  • 422 vehicleAssignmentNotFound — driver has no active vehicle assignment
  • 422 shipmentStopAlreadyOnRoute — a stop is already on another active/assigned route
  • 422 deliveryStopBeforePickupStop — delivery stop appears before pickup for the same shipment

Example request body:

JSON
{
    "driver_id": 1,
    "route_date": "2026-06-25",
    "stops": [10, 11, 15]
}

Update Request Body

Field Type Required Validation
driver_id integer No Must exist; re-resolves vehicle_id from new driver's assignment
route_date date No Recalculates stop metrics when changed
status string No See RouteStatuses
stops array No Replaces all stops (ordered IDs); recalculates metrics

Not accepted on update: vehicle_id, total_stops, optimized_distance, duration — always calculated by the server when stops or driver/date change.

Only allowed when route is not in a terminal status (completed, cancelled).

Plan Routes Request

POST /api/admin/routes/plan

Field Type Required Validation
date date Yes Planning date
driver_ids array No Optional filter; each must exist in drivers

Dispatches an async job. Poll GET /api/admin/routes/plan/result?date=YYYY-MM-DD for the summary.

Route Resource Shape

JSON
{
    "id": 1,
    "route_number": "AB12CD34",
    "route_date": "2026-06-25T00:00:00.000000Z",
    "status": "assigned",
    "status_label": "تم تخصيص السائق",
    "total_stops": 3,
    "optimized_distance": 42.5,
    "duration": 95.0,
    "driver": { "...": "DriverResource when loaded" },
    "vehicle": { "...": "VehicleResource when loaded" },
    "stops": [{ "...": "RouteStopResource when loaded" }],
    "created_at": "2026-06-24T20:00:00.000000Z",
    "updated_at": "2026-06-24T20:00:00.000000Z"
}

driver, vehicle, and stops are only present when the resource is loaded via GET /routes/{id} or list endpoints that eager-load relations.

Route Assignments

Tracks driver assignment history for a route. A driver can be assigned and later unassigned when the route is reassigned.

Permission prefix: RouteAssignment

Method Path Description Permission
GET /api/admin/routes/{id}/assignments List assignments for route ViewAny:RouteAssignment

Route Assignment Resource Shape

JSON
{
    "id": 1,
    "assigned_at": "2026-06-01T08:00:00.000000Z",
    "unassigned_at": null,
    "driver": {
        "id": 3,
        "driver_number": "482910",
        "name": "mousa"
    },
    "created_at": "2026-06-01T08:00:00.000000Z",
    "updated_at": "2026-06-01T08:00:00.000000Z"
}
Field Notes
driver Nested DriverResource when relation is loaded
unassigned_at null = currently assigned to this route

Route Stops

Individual ordered stops on a driver route, linked to shipment stops.

Permission prefix: Route

Endpoints

Method Path Description Permission
GET /api/admin/route-stops/{id} Get single route stop ViewAny:Route
POST /api/admin/route-stops/{id} Update stop status/times Update:Route
DELETE /api/admin/route-stops/{id} Delete stop Delete:Route

Route stops are created automatically when a route is created or updated via POST /api/admin/routes. Use the route-level endpoints to manage the full stop list.

Update Request Body

Field Type Required Validation
status string No See RouteStopStatuses
arrived_at datetime No
completed_at datetime No

RouteStop Resource Shape

JSON
{
    "id": 1,
    "route_id": 1,
    "shipment_stop_id": 10,
    "sequence": 1,
    "status": "pending",
    "status_label": "قيد الانتظار",
    "stop_type": "pickup",
    "stop_type_label": "تحصيل",
    "lat": 24.7136,
    "lng": 46.6753,
    "full_address": "Riyadh, SA",
    "contact_name": "Ali Hassan",
    "phone": "0501234567",
    "shipment": { "...": "ShipmentResource when loaded" },
    "distance_from_previous_km": 5.2,
    "estimated_minutes_from_previous": 12.5,
    "estimated_arrival_at": "2026-06-25T08:12:00.000000Z",
    "estimated_departure_at": "2026-06-25T08:27:00.000000Z",
    "arrived_at": null,
    "completed_at": null,
    "created_at": "2026-06-24T20:00:00.000000Z",
    "updated_at": "2026-06-24T20:00:00.000000Z"
}

Domain Models Reference

Currently Exposed via API

Model Table Description Key Fields
Admin admins Back-office users name, email, password, fcm_token
Role roles Spatie roles (guard: admin) name, guard_name
Permission permissions Spatie permissions name, guard_name
Driver drivers Delivery drivers driver_number, name, phone, working_days, work_start_time, work_end_time, lat, lng, is_active
Vehicle vehicles Fleet vehicles plate_number, owner_type, owner_id, max_weight, max_volume, is_active
DriverVehicleAssignment driver_vehicle_assignments Driver ↔ vehicle link driver_id, vehicle_id, assigned_at, released_at
DriverUnavailability driver_unavailabilities Driver time-off driver_id, start_date, end_date, reason
User users Customers / shippers name, phone, phone_country, email, company_name
Shipment shipments Delivery orders shipment_number, user_id, status, type, payment_type, delivery_date
ShipmentItem shipment_items Items within a shipment name, quantity, weight, declared_value
ShipmentStop shipment_stops Pickup / delivery stops type, lat, lng, sequence
ShipmentEvent shipment_events Event audit log event_type, data, causer_type, causer_id
ProofOfDelivery proof_of_deliveries Delivery confirmation receiver_name, delivered_at
FailedDelivery failed_deliveries Failed delivery attempts reason_code, notes
Route routes Daily driver routes route_number, driver_id, vehicle_id, route_date, status, total_stops, optimized_distance, duration
RouteStop route_stops Ordered stops on a route shipment_stop_id, sequence, status, estimated_arrival_at, estimated_departure_at
RouteAssignment route_assignments Driver assignment history on a route route_id, driver_id, assigned_at, unassigned_at

Planned Domain (Not Yet in API)

These models exist in the database but have no dedicated HTTP endpoints yet.

StaticContent (static_contents)

Key-value CMS / app configuration store (not yet in API).

Field Type Notes
key string Primary key; see StaticContentTypes
value string Plain text or JSON-encoded value

Entity Relationship Diagram (Overview)

TEXT
User ──< Shipment ──< ShipmentItem
                  ──< ShipmentStop ──< RouteStop >── Route >── Driver
                  ──< ShipmentEvent                  └── Vehicle
                  ──< ProofOfDelivery
                  ──< FailedDelivery

Driver ──< DriverVehicleAssignment >── Vehicle
       ──< DriverUnavailability
       ──< RouteAssignment >── Route

Admin ──< Role ──< Permission

Enums Reference

All enum values are snake_case strings. Store them as string constants in the frontend.

OwnerTypes

Used in API today — vehicle ownership.

Value Arabic Label Rules
company الشركة owner_id must be null
driver سائق owner_id must be a valid driver ID

API returns both owner_type (value) and owner_type_label (Arabic label).

Suggested TypeScript:

TYPESCRIPT
type OwnerType = "company" | "driver";

ShipmentStatuses

Shipment lifecycle state — returned in status and status_label fields. Map UI colors client-side using the table below.

Value Arabic Label Suggested UI Color
draft مسودة secondary
pending في الانتظار warning
assigned تم التخصيص لسائق primary
pickup_in_progress قيد التحصيل warning
picked_up تم التحصيل success
at_hub في المخزن info
out_for_delivery قيد التسليم primary
delivered تم التسليم success
delivery_failed فشل التسليم danger
returned مرتجع warning
cancelled تم الإلغاء danger

Typical flow:

TEXT
draft → pending → assigned → pickup_in_progress → picked_up → at_hub
  → out_for_delivery → delivered
                                    ↘ delivery_failed → returned
Any state → cancelled

ShipmentTypes

Value Arabic Label
domestic محلي
international دولية

ShipmentPriorities

Used in API today — shipment urgency for planning and filtering.

Value Arabic Label Suggested UI Color
low منخفضة secondary
normal عادية primary
high عالية warning
urgent عاجلة danger

Defaults to normal on create when omitted. Higher priority shipments are planned first during route planning.

Suggested TypeScript:

TYPESCRIPT
type ShipmentPriority = "low" | "normal" | "high" | "urgent";

ShipmentStopTypes

Value Arabic Label Description
pickup تحصيل Collect goods from sender
delivery تسليم Deliver to recipient
stop موقف Intermediate stop

PaymentTypes

Value Arabic Label Notes
cod عند الاستلام Cash on delivery; requires cod_amount
prepaid مقدما Paid upfront

RouteStatuses

Value Arabic Label
draft مسودة
assigned تم تخصيص السائق
active فعال
completed مكتمل
cancelled ملغي

RouteStopStatuses

Value Arabic Label
pending قيد الانتظار
arrived تم الوصول
completed مكتمل
failed فشل
skipped تخطي

ShipmentEvents

Timeline event types logged on shipments. Each event has a structured data JSON payload.

Value Arabic Label data Keys
shipment_created تم إنشاء الشحنة shipment_number, type, payment_type, delivery_date
status_changed تم تغيير الحالة from_status, to_status
route_assigned تم تعيين المسار route_id, driver_id, driver_name
route_unassigned تم إلغاء تعيين المسار route_id, reason
driver_assigned تم تخصيص السائق driver_id, driver_name
route_created تم إنشاء الرحلة route_id
pickup_started تم بدء التحصيل stop_id, driver_id, driver_name
pickup_completed تم إنهاء التحصيل stop_id, arrived_at, completed_at
out_for_delivery خرجت للتسليم route_id, driver_id, driver_name
delivery_failed فشل التسليم stop_id, reason_code, notes
delivered تم التسليم stop_id, receiver_name, delivered_at
pod_uploaded تم تحميل إثبات التوصيل receiver_name, delivered_at, notes
cancelled تم الإلغاء reason, cancelled_by_type, cancelled_by_id
returned تم الإرجاع reason, returned_at

FailedDeliveryReasons

Value Arabic Label
customer_unavailable العميل غير متاح
wrong_address عنوان خاطئ
customer_refused العميل رفض الاستلام
unable_to_contact تعذر التواصل مع العميل
damaged_package الطرد تالف
other أخرى

MediaTypes

Internal media collection names (used for image uploads).

Value Used For
admin_image Admin profile photos
driver_image Driver profile photos
user_image User profile photos (future)

StaticContentTypes

CMS / app configuration keys (not yet in API).

Value Arabic Label
privacy_policy سياسة الخصوصية
commission عمولة الإدارة
social_media المواقع الاجتماعية
android_app_version نسخة التطبيق للاندرويد
ios_app_version نسخة التطبيق لل IOS
android_provider_app_version نسخة التطبيق للاندرويد للمزود
ios_provider_app_version نسخة التطبيق لل IOS للمزود

NotificationTypes

Push notification categories (PHP backed enum).

Value
general
wallet_deposit
wallet_withdraw
new_offer
offer_accepted
offer_rejected
order_status_changed
new_order

Working Days (not a PHP enum)

Driver schedule days. Accepted as array values in create/update requests.

Value Day
sun Sunday
mon Monday
tue Tuesday
wed Wednesday
thu Thursday
fri Friday
sat Saturday

Suggested TypeScript:

TYPESCRIPT
type WorkingDay = "sun" | "mon" | "tue" | "wed" | "thu" | "fri" | "sat";

Permissions Reference

Permissions control what each admin can do. Check them client-side to show/hide UI elements.

Naming Convention

TEXT
{Action}:{Model}
Action Description
ViewAny List/index access
View View single record
Create Create new record
Update Edit existing record
Delete Delete record
Reorder Reorder records (if applicable)

Examples:

  • ViewAny:Driver — can list drivers
  • Create:Vehicle — can create vehicles
  • Update:Admin — can edit admins
  • Delete:Role — can delete roles

Custom Permissions

These do not map to a CRUD model:

Permission Description
Export:Reports Export reports
Send:Notifications Send push notifications
Manage:AppSettings Manage app settings
Assign:Routes Assign routes to drivers
Track:Deliveries Track live deliveries

How to Check Permissions on Frontend

After login, inspect data.admin.permission_groups:

TYPESCRIPT
function hasPermission(
    permissionGroups: PermissionGroup[],
    permissionName: string,
): boolean {
    return permissionGroups.some((group) =>
        group.permissions.some((p) => p.name === permissionName),
    );
}

// Usage
if (hasPermission(admin.permission_groups, "Create:Driver")) {
    // Show "Add Driver" button
}

Super admin role has all permissions implicitly.


Error Handling

HTTP Status Codes

Code Meaning When
200 Success Normal operation
401 Unauthorized Missing/invalid permission, wrong credentials
403 Forbidden Not authenticated (no/invalid token)
404 Not Found Route or resource not found
422 Validation Error Invalid request body/query
500 Server Error Unexpected failure

Common Error Messages

Message Key English Text Status
credentialsError Wrong Credentials 401
Unauthenticated Please login first 403
Unauthorized You do not have permissions to perform this action 401
cannotDeleteCurrentAdmin You cannot delete your own admin account 403
cannotModifySuperAdmin Super admin role cannot be modified 403
vehicleAlreadyAssigned This vehicle is already assigned to a driver 422
vehicleAssignmentNotFound No active vehicle assignment found for this driver 404
couldNotCreateToken Could not create authentication token 500
couldNotLogout Could not log out 500
cannotUpdateShipment Shipment cannot be updated in its current status 422
cannotConfirmShipment Only draft shipments can be confirmed 422
shipmentAlreadyCancelled Shipment is already in a terminal status and cannot be cancelled 422
cannotDeleteShipment Only draft shipments can be deleted 422
shipmentNotPending Shipment must be in pending status to assign a route 422
shipmentNotAssigned Shipment must be in assigned status to unassign from a route 422
invalidShipmentStatus Invalid shipment status provided 422

Validation errors return the first validation message as message.

Error Response Shape

JSON
{
    "status": "Error",
    "message": "Wrong Credentials",
    "data": null,
    "statusCode": 401
}

Frontend Integration Checklist

  • Store JWT from login response (data.token)
  • Send Authorization: Bearer {token} on every authenticated request
  • Set Accept-Language: en or ar for localized messages
  • Use POST for all updates (not PUT/PATCH)
  • Use multipart/form-data when uploading image fields
  • Gate UI by checking permission_groups from login/profile response
  • Handle the standard { status, message, data, statusCode } envelope
  • Use paginated endpoints with page query param for tables
  • Pass search, sort_by, sort_direction for filterable lists
  • For vehicles: enforce owner_id rules based on owner_type
  • For drivers: send working_days as array; times as HH:MM or HH:MM:SS
  • For shipments: ensure exactly 1 pickup stop and 1 delivery stop in the stops array
  • For shipments: cod_amount is required when payment_type = cod
  • Handle status_changed events from GET /shipments/{id}/events for timeline display
  • Route assignment: shipment must be pending before assign-route; assigned before unassign-route
  • Route create: send driver_id + stops[]; do not send vehicle_id, status, or metrics — server calculates them
  • Route create requires driver to have an active vehicle assignment
  • Route planning: dispatch via POST /routes/plan, poll GET /routes/plan/result?date=

Route Index (Quick Reference)

Method Path
POST /api/admin/login
POST /api/admin/logout
GET /api/admin/profile
POST /api/admin/profile
GET /api/admin/dashboard/overview
GET /api/admin/dashboard/trends
GET /api/admin/permissions
GET/POST/DELETE /api/admin/roles, /api/admin/roles/paginated, /api/admin/roles/{id}, /api/admin/roles/{id}/permissions
GET/POST/DELETE /api/admin/admins, /api/admin/admins/paginated, /api/admin/admins/{id}
GET/POST/DELETE /api/admin/drivers, /api/admin/drivers/paginated, /api/admin/drivers/{id}
GET/POST /api/admin/drivers/{id}/vehicle-assignments, .../assign, .../unassign
GET /api/admin/drivers/{id}/unavailabilities
GET/POST/DELETE /api/admin/vehicles, /api/admin/vehicles/paginated, /api/admin/vehicles/{id}
GET/POST/DELETE /api/admin/driver-unavailabilities, .../paginated, .../{id}
GET/POST/DELETE /api/admin/users, /api/admin/users/paginated, /api/admin/users/{id}
GET/POST/DELETE /api/admin/shipments, /api/admin/shipments/paginated, /api/admin/shipments/{id}
POST /api/admin/shipments/create-with-user
POST /api/admin/shipments/{id}/confirm, /api/admin/shipments/{id}/cancel, /api/admin/shipments/{id}/status
POST /api/admin/shipments/{id}/assign-route, /api/admin/shipments/{id}/unassign-route
GET /api/admin/shipments/{id}/events
POST /api/admin/shipments/bulk/cancel, /api/admin/shipments/bulk/confirm, /api/admin/shipments/bulk/assign-route
GET /api/admin/shipment-stops, /api/admin/shipment-stops/paginated, /api/admin/shipment-stops/{id}
GET/POST /api/admin/shipments/{id}/proof-of-deliveries
GET /api/admin/proof-of-deliveries/{id}
GET/POST /api/admin/shipments/{id}/failed-deliveries
GET /api/admin/failed-deliveries/{id}
GET/POST/DELETE /api/admin/routes, /api/admin/routes/paginated, /api/admin/routes/{id}
POST /api/admin/routes/{id}/start, /api/admin/routes/{id}/complete, /api/admin/routes/{id}/cancel
GET /api/admin/routes/{id}/stops, /api/admin/routes/{id}/assignments
POST/GET /api/admin/routes/plan, /api/admin/routes/plan/result
GET/POST/DELETE /api/admin/route-stops/{id}

Driver App API

The Driver App API (/api/driver/*) is a separate, mobile-first API for drivers. It uses the same JWT mechanism as the Admin API but with the driver guard. Tokens never expire (expires_in: null).

Driver Authentication

Method Endpoint Auth Description
POST /api/driver/login Public Login with phone, phone_country, password. Optionally pass fcm_token. Returns JWT + driver profile.
POST /api/driver/logout Required Invalidate the current token.

Login request:

JSON
{
    "phone": "0512345678",
    "phone_country": "SA",
    "password": "secret",
    "fcm_token": null
}

Login response:

JSON
{
  "data": {
    "token": "eyJ...",
    "token_type": "bearer",
    "expires_in": null,
    "driver": { "id": 1, "name": "...", "phone": "...", ... }
  }
}

Driver Profile

Method Endpoint Description
GET /api/driver/profile Get full driver profile including assigned vehicle.
POST /api/driver/profile Update name, email, password (all optional, password min 6 chars).
POST /api/driver/fcm-token Update push notification token. Body: { "fcm_token": "..." }
POST /api/driver/location Update GPS coordinates. Body: { "lat": 24.71, "lng": 46.67 }

Driver Routes

All route endpoints are automatically scoped to the authenticated driver. A driver has one route per day.

Method Endpoint Description
GET /api/driver/routes Paginated list (15/page). Filters: ?status=assigned&date=2026-07-02
GET /api/driver/routes/today Today's route. Returns 404 if no route assigned for today.
GET /api/driver/routes/{id} Full route with stops. Each stop's shipment includes items so the driver knows what to pick up or deliver. Returns 404 if route does not belong to driver.
POST /api/driver/routes/{id}/start Start route: assigned → active.
POST /api/driver/routes/{id}/complete Complete route: active → completed.

Driver Route Stops

All stop endpoints validate that the stop belongs to a route owned by the authenticated driver.

Method Endpoint Description
GET /api/driver/routes/{routeId}/stops All stops for a route, ordered by sequence. Includes address, items, shipment.
GET /api/driver/routes/{routeId}/stops/{stopId} Full stop details with ETA fields. shipment.items lists what to pick up or deliver.
POST /api/driver/routes/{routeId}/stops/{stopId}/arrive Mark arrived: pending → arrived. Sets arrived_at. Route must be active (started) first.
POST /api/driver/routes/{routeId}/stops/{stopId}/complete Complete stop. See logic below.
POST /api/driver/routes/{routeId}/stops/{stopId}/fail Fail a delivery stop. Body: reason_code, notes?.
POST /api/driver/routes/{routeId}/stops/{stopId}/skip Skip stop (pending or arrived).

Complete stop logic by stop_type:

Stop Type What happens
delivery Creates ProofOfDelivery, sets shipment → delivered, fires delivered event. Body: receiver_name, receiver_phone, notes?
pickup Sets shipment → picked_up, fires pickup_completed event. No body required.

Fail stop — reason_code values: customer_unavailable, wrong_address, customer_refused, unable_to_contact, damaged_package, other.

Driver Route Stop Status Flow

TEXT
pending → arrived → completed (via complete)
                  → failed    (via fail)
pending → skipped (via skip)
arrived → skipped (via skip)

Driver API Quick Reference

Method Path
POST /api/driver/login
POST /api/driver/logout
GET /api/driver/profile
POST /api/driver/profile
POST /api/driver/fcm-token
POST /api/driver/location
GET /api/driver/routes
GET /api/driver/routes/today
GET /api/driver/routes/{id}
POST /api/driver/routes/{id}/start
POST /api/driver/routes/{id}/complete
GET /api/driver/routes/{routeId}/stops
GET /api/driver/routes/{routeId}/stops/{stopId}
POST /api/driver/routes/{routeId}/stops/{stopId}/arrive
POST /api/driver/routes/{routeId}/stops/{stopId}/complete
POST /api/driver/routes/{routeId}/stops/{stopId}/fail
POST /api/driver/routes/{routeId}/stops/{stopId}/skip

Generated from the shipping_system codebase. Last updated: July 2026.