PHPackages                             erikgall/motive-sdk - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [API Development](/categories/api)
4. /
5. erikgall/motive-sdk

ActiveLibrary[API Development](/categories/api)

erikgall/motive-sdk
===================

v1.0.0(4mo ago)0268↓33.3%PHPCI passing

Since Jan 18Pushed 4mo agoCompare

[ Source](https://github.com/erikgall/motive-sdk)[ Packagist](https://packagist.org/packages/erikgall/motive-sdk)[ RSS](/packages/erikgall-motive-sdk/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (1)Dependencies (8)Versions (2)Used By (0)

Motive ELD Laravel SDK
======================

[](#motive-eld-laravel-sdk)

A first-party-quality Laravel SDK for the [Motive ELD API](https://developer.gomotive.com/) featuring expressive, elegant syntax with fluent, chainable methods.

Requirements
------------

[](#requirements)

- PHP 8.2+
- Laravel 11+

Installation
------------

[](#installation)

```
composer require erikgall/motive-sdk
```

Publish the configuration file:

```
php artisan vendor:publish --tag=motive-config
```

Configuration
-------------

[](#configuration)

Add your Motive API credentials to your `.env` file:

```
# API Key Authentication
MOTIVE_API_KEY=your-api-key

# OAuth Authentication (optional)
MOTIVE_CLIENT_ID=your-client-id
MOTIVE_CLIENT_SECRET=your-client-secret
MOTIVE_REDIRECT_URI=https://your-app.com/motive/callback

# Webhook Secret (optional)
MOTIVE_WEBHOOK_SECRET=your-webhook-secret

# Optional Settings
MOTIVE_TIMEZONE=America/Chicago
MOTIVE_METRIC_UNITS=false
```

### Configuration File

[](#configuration-file)

```
// config/motive.php
return [
    'default' => env('MOTIVE_CONNECTION', 'default'),

    'connections' => [
        'default' => [
            'auth_driver' => env('MOTIVE_AUTH_DRIVER', 'api_key'),
            'api_key' => env('MOTIVE_API_KEY'),
            'oauth' => [
                'client_id' => env('MOTIVE_CLIENT_ID'),
                'client_secret' => env('MOTIVE_CLIENT_SECRET'),
                'redirect_uri' => env('MOTIVE_REDIRECT_URI'),
            ],
            'base_url' => env('MOTIVE_BASE_URL', 'https://api.gomotive.com'),
            'timeout' => 30,
            'retry' => ['times' => 3, 'sleep' => 100],
        ],
    ],

    'headers' => [
        'timezone' => env('MOTIVE_TIMEZONE'),
        'metric_units' => env('MOTIVE_METRIC_UNITS', false),
    ],

    'webhooks' => [
        'secret' => env('MOTIVE_WEBHOOK_SECRET'),
        'tolerance' => 300,
    ],
];
```

Basic Usage
-----------

[](#basic-usage)

### Using the Facade

[](#using-the-facade)

```
use Motive\Facades\Motive;

// List all vehicles
$vehicles = Motive::vehicles()->list();

foreach ($vehicles as $vehicle) {
    echo "{$vehicle->number}: {$vehicle->make} {$vehicle->model}\n";
}
```

### Using Dependency Injection

[](#using-dependency-injection)

```
use Motive\MotiveManager;

class VehicleController extends Controller
{
    public function __construct(
        protected MotiveManager $motive
    ) {}

    public function index()
    {
        return $this->motive->vehicles()->list();
    }
}
```

---

Vehicles
--------

[](#vehicles)

### List All Vehicles

[](#list-all-vehicles)

```
use Motive\Facades\Motive;

// Lazy pagination - automatically fetches pages as needed
$vehicles = Motive::vehicles()->list();

foreach ($vehicles as $vehicle) {
    echo $vehicle->number;
    echo $vehicle->make;
    echo $vehicle->model;
    echo $vehicle->vin;
    echo $vehicle->status->value; // VehicleStatus enum
}

// With filters
$vehicles = Motive::vehicles()->list([
    'status' => 'active',
    'per_page' => 50,
]);
```

### Paginate Vehicles

[](#paginate-vehicles)

```
// Get a specific page with metadata
$page = Motive::vehicles()->paginate(page: 1, perPage: 25);

echo "Showing {$page->count()} of {$page->total()} vehicles";
echo "Page {$page->currentPage()} of {$page->lastPage()}";

foreach ($page->items() as $vehicle) {
    echo $vehicle->number;
}

// Check for more pages
if ($page->hasMorePages()) {
    $nextPage = Motive::vehicles()->paginate(page: $page->currentPage() + 1);
}
```

### Find a Vehicle

[](#find-a-vehicle)

```
// By ID
$vehicle = Motive::vehicles()->find(123);

// By vehicle number
$vehicle = Motive::vehicles()->findByNumber('TRUCK-001');

// By external ID
$vehicle = Motive::vehicles()->findByExternalId('ext-123');
```

### Create a Vehicle

[](#create-a-vehicle)

```
$vehicle = Motive::vehicles()->create([
    'number' => 'TRUCK-042',
    'make' => 'Freightliner',
    'model' => 'Cascadia',
    'year' => 2024,
    'vin' => '1FUJGLDR5CLBP8834',
    'license_plate_number' => 'ABC1234',
    'license_plate_state' => 'TX',
]);

echo "Created vehicle #{$vehicle->id}";
```

### Update a Vehicle

[](#update-a-vehicle)

```
$vehicle = Motive::vehicles()->update(123, [
    'number' => 'TRUCK-042-UPDATED',
    'license_plate_number' => 'XYZ9876',
]);
```

### Delete a Vehicle

[](#delete-a-vehicle)

```
$deleted = Motive::vehicles()->delete(123);

if ($deleted) {
    echo "Vehicle deleted successfully";
}
```

### Get Current Location

[](#get-current-location)

```
$location = Motive::vehicles()->currentLocation(123);

echo "Lat: {$location->latitude}, Lng: {$location->longitude}";
echo "Speed: {$location->speed} mph";
echo "Heading: {$location->bearing}";
echo "Updated: {$location->locatedAt->diffForHumans()}";
```

### Get Location History

[](#get-location-history)

```
$locations = Motive::vehicles()->locations(123, [
    'start_date' => now()->subDays(7)->toDateString(),
    'end_date' => now()->toDateString(),
]);

foreach ($locations as $location) {
    echo "{$location->locatedAt}: ({$location->latitude}, {$location->longitude})";
}
```

---

Users &amp; Drivers
-------------------

[](#users--drivers)

### List Users

[](#list-users)

```
$users = Motive::users()->list();

foreach ($users as $user) {
    echo $user->firstName . ' ' . $user->lastName;
    echo $user->email;
    echo $user->role;
}

// Filter by role
$drivers = Motive::users()->list(['role' => 'driver']);
```

### Find a User

[](#find-a-user)

```
$user = Motive::users()->find(456);

// Access driver-specific data
if ($user->driver) {
    echo "Driver License: {$user->driver->licenseNumber}";
    echo "License State: {$user->driver->licenseState}";
}
```

### Create a User

[](#create-a-user)

```
$user = Motive::users()->create([
    'first_name' => 'John',
    'last_name' => 'Doe',
    'email' => 'john.doe@example.com',
    'phone' => '555-123-4567',
    'role' => 'driver',
    'driver' => [
        'license_number' => 'DL123456',
        'license_state' => 'TX',
    ],
]);
```

### Update a User

[](#update-a-user)

```
$user = Motive::users()->update(456, [
    'phone' => '555-987-6543',
]);
```

### Deactivate/Reactivate a User

[](#deactivatereactivate-a-user)

```
// Deactivate
Motive::users()->deactivate(456);

// Reactivate
Motive::users()->reactivate(456);
```

---

Hours of Service (HOS)
----------------------

[](#hours-of-service-hos)

### Get HOS Logs

[](#get-hos-logs)

```
use Motive\Enums\DutyStatus;

$logs = Motive::hosLogs()->list([
    'driver_ids' => [123, 456],
    'start_date' => now()->subDays(7)->toDateString(),
    'end_date' => now()->toDateString(),
]);

foreach ($logs as $log) {
    echo $log->driver->firstName . ' ' . $log->driver->lastName;
    echo $log->status->value; // DutyStatus enum
    echo $log->startTime->format('Y-m-d H:i:s');
    echo $log->duration . ' minutes';
    echo $log->location;
}

// Filter by duty status
$drivingLogs = Motive::hosLogs()->list([
    'driver_ids' => [123],
    'duty_status' => DutyStatus::Driving->value,
]);
```

### Get HOS Availability

[](#get-hos-availability)

```
$availability = Motive::hosAvailability()->forDriver(123);

echo "Drive time remaining: {$availability->driveTimeRemaining} minutes";
echo "Shift time remaining: {$availability->shiftTimeRemaining} minutes";
echo "Cycle time remaining: {$availability->cycleTimeRemaining} minutes";
echo "Break time required in: {$availability->breakTimeRequired} minutes";

// Check if driver can drive
if ($availability->driveTimeRemaining > 0 && $availability->shiftTimeRemaining > 0) {
    echo "Driver is available to drive";
}
```

### Get HOS Availability for Multiple Drivers

[](#get-hos-availability-for-multiple-drivers)

```
$availabilities = Motive::hosAvailability()->list([
    'driver_ids' => [123, 456, 789],
]);

foreach ($availabilities as $availability) {
    echo "{$availability->driver->firstName}: {$availability->driveTimeRemaining} min remaining";
}
```

### Get HOS Violations

[](#get-hos-violations)

```
$violations = Motive::hosViolations()->list([
    'driver_ids' => [123],
    'start_date' => now()->subDays(30)->toDateString(),
]);

foreach ($violations as $violation) {
    echo $violation->type; // e.g., "11_hour", "14_hour", "30_minute_break"
    echo $violation->startTime->format('Y-m-d H:i');
    echo $violation->duration . ' minutes';
}
```

### Create/Edit HOS Log Entry

[](#createedit-hos-log-entry)

```
// Create a new log entry
$log = Motive::hosLogs()->create([
    'driver_id' => 123,
    'status' => DutyStatus::OnDuty->value,
    'start_time' => now()->toIso8601String(),
    'location' => 'Dallas, TX',
    'notes' => 'Pre-trip inspection',
]);

// Edit an existing entry (with annotation)
$log = Motive::hosLogs()->update($log->id, [
    'status' => DutyStatus::Driving->value,
    'annotation' => 'Corrected status from On Duty to Driving',
]);
```

---

Dispatches
----------

[](#dispatches)

### List Dispatches

[](#list-dispatches)

```
use Motive\Enums\DispatchStatus;

$dispatches = Motive::dispatches()->list([
    'status' => DispatchStatus::InProgress->value,
]);

foreach ($dispatches as $dispatch) {
    echo "#{$dispatch->externalId}: {$dispatch->status->value}";
    echo "Driver: {$dispatch->driver->firstName} {$dispatch->driver->lastName}";

    foreach ($dispatch->stops as $stop) {
        echo "  - {$stop->type}: {$stop->address}";
    }
}
```

### Create a Dispatch

[](#create-a-dispatch)

```
$dispatch = Motive::dispatches()->create([
    'external_id' => 'ORDER-12345',
    'driver_id' => 123,
    'vehicle_id' => 456,
    'notes' => 'Handle with care - fragile items',
]);

echo "Created dispatch #{$dispatch->id}";
```

### Add Stops to a Dispatch

[](#add-stops-to-a-dispatch)

```
// Add pickup location
$pickup = Motive::dispatchLocations()->create($dispatch->id, [
    'type' => 'pickup',
    'name' => 'Warehouse A',
    'address' => '123 Industrial Blvd',
    'city' => 'Dallas',
    'state' => 'TX',
    'postal_code' => '75201',
    'scheduled_arrival' => now()->addHours(2)->toIso8601String(),
    'notes' => 'Dock 5',
]);

// Add delivery location
$delivery = Motive::dispatchLocations()->create($dispatch->id, [
    'type' => 'delivery',
    'name' => 'Customer Site',
    'address' => '456 Commerce St',
    'city' => 'Houston',
    'state' => 'TX',
    'postal_code' => '77001',
    'scheduled_arrival' => now()->addHours(8)->toIso8601String(),
]);
```

### Update Dispatch Status

[](#update-dispatch-status)

```
$dispatch = Motive::dispatches()->update($dispatch->id, [
    'status' => DispatchStatus::Completed->value,
]);
```

---

Assets &amp; Trailers
---------------------

[](#assets--trailers)

### List Assets

[](#list-assets)

```
$assets = Motive::assets()->list();

foreach ($assets as $asset) {
    echo $asset->name;
    echo $asset->assetType; // 'trailer', 'container', etc.
    echo $asset->status->value;
}
```

### Create an Asset

[](#create-an-asset)

```
$asset = Motive::assets()->create([
    'name' => 'TRAILER-001',
    'asset_type' => 'trailer',
    'make' => 'Great Dane',
    'model' => 'Everest',
    'year' => 2023,
    'vin' => '1GRAA0622DB500001',
    'license_plate_number' => 'TRL1234',
    'license_plate_state' => 'TX',
]);
```

### Assign Asset to Vehicle

[](#assign-asset-to-vehicle)

```
Motive::assets()->assignToVehicle($asset->id, $vehicle->id);

// Unassign
Motive::assets()->unassignFromVehicle($asset->id);
```

---

Locations &amp; Geofences
-------------------------

[](#locations--geofences)

### List Locations

[](#list-locations)

```
$locations = Motive::locations()->list();

foreach ($locations as $location) {
    echo $location->name;
    echo $location->address;
    echo "({$location->latitude}, {$location->longitude})";
}
```

### Create a Location

[](#create-a-location)

```
$location = Motive::locations()->create([
    'name' => 'Dallas Distribution Center',
    'address' => '123 Logistics Way',
    'city' => 'Dallas',
    'state' => 'TX',
    'postal_code' => '75201',
    'latitude' => 32.7767,
    'longitude' => -96.7970,
]);
```

### List Geofences

[](#list-geofences)

```
$geofences = Motive::geofences()->list();

foreach ($geofences as $geofence) {
    echo $geofence->name;
    echo $geofence->type; // 'circle', 'polygon'
    echo "Radius: {$geofence->radius} meters";
}
```

### Create a Circular Geofence

[](#create-a-circular-geofence)

```
$geofence = Motive::geofences()->create([
    'name' => 'Customer Site A',
    'type' => 'circle',
    'latitude' => 32.7767,
    'longitude' => -96.7970,
    'radius' => 500, // meters
    'notes' => 'Automatic arrival detection',
]);
```

### Create a Polygon Geofence

[](#create-a-polygon-geofence)

```
$geofence = Motive::geofences()->create([
    'name' => 'Warehouse Complex',
    'type' => 'polygon',
    'coordinates' => [
        ['latitude' => 32.7767, 'longitude' => -96.7970],
        ['latitude' => 32.7770, 'longitude' => -96.7965],
        ['latitude' => 32.7765, 'longitude' => -96.7960],
        ['latitude' => 32.7760, 'longitude' => -96.7968],
    ],
]);
```

---

Inspection Reports (DVIR)
-------------------------

[](#inspection-reports-dvir)

### List Inspection Reports

[](#list-inspection-reports)

```
$reports = Motive::inspectionReports()->list([
    'start_date' => now()->subDays(7)->toDateString(),
    'vehicle_id' => 123,
]);

foreach ($reports as $report) {
    echo $report->type; // 'pre_trip', 'post_trip'
    echo $report->vehicle->number;
    echo $report->driver->firstName;
    echo $report->status; // 'satisfactory', 'defects_found'
    echo $report->createdAt->format('Y-m-d H:i');

    if ($report->defects) {
        foreach ($report->defects as $defect) {
            echo "  Defect: {$defect->area} - {$defect->description}";
        }
    }
}
```

### Get a Specific Report

[](#get-a-specific-report)

```
$report = Motive::inspectionReports()->find($reportId);

// Download the signed PDF
$pdf = Motive::inspectionReports()->downloadPdf($reportId);
file_put_contents('inspection-report.pdf', $pdf);
```

---

Documents
---------

[](#documents)

### List Documents

[](#list-documents)

```
$documents = Motive::documents()->list([
    'driver_id' => 123,
    'status' => 'pending',
]);

foreach ($documents as $document) {
    echo $document->name;
    echo $document->type;
    echo $document->status->value;
}
```

### Upload a Document

[](#upload-a-document)

```
$document = Motive::documents()->upload([
    'driver_id' => 123,
    'name' => 'Bill of Lading - Order 12345',
    'type' => 'bill_of_lading',
    'file' => fopen('/path/to/document.pdf', 'r'),
]);
```

### Download a Document

[](#download-a-document)

```
$content = Motive::documents()->download($document->id);
file_put_contents('downloaded-document.pdf', $content);
```

---

Messages
--------

[](#messages)

### List Messages

[](#list-messages)

```
$messages = Motive::messages()->list([
    'driver_id' => 123,
]);

foreach ($messages as $message) {
    echo $message->direction; // 'inbound', 'outbound'
    echo $message->body;
    echo $message->sentAt->format('Y-m-d H:i');
}
```

### Send a Message to a Driver

[](#send-a-message-to-a-driver)

```
$message = Motive::messages()->send([
    'driver_id' => 123,
    'body' => 'Please call dispatch when you arrive.',
]);
```

### Send a Broadcast Message

[](#send-a-broadcast-message)

```
$message = Motive::messages()->broadcast([
    'driver_ids' => [123, 456, 789],
    'body' => 'Weather alert: Severe storms expected on I-35 corridor.',
]);
```

---

Fuel Purchases
--------------

[](#fuel-purchases)

### List Fuel Purchases

[](#list-fuel-purchases)

```
$purchases = Motive::fuelPurchases()->list([
    'start_date' => now()->subDays(30)->toDateString(),
    'vehicle_id' => 123,
]);

foreach ($purchases as $purchase) {
    echo $purchase->vehicle->number;
    echo $purchase->gallons . ' gallons';
    echo '$' . $purchase->totalAmount;
    echo $purchase->location;
    echo $purchase->purchasedAt->format('Y-m-d');
}
```

### Create a Fuel Purchase

[](#create-a-fuel-purchase)

```
$purchase = Motive::fuelPurchases()->create([
    'vehicle_id' => 123,
    'driver_id' => 456,
    'gallons' => 150.5,
    'price_per_gallon' => 3.459,
    'total_amount' => 520.58,
    'odometer' => 125430,
    'location' => 'Pilot Travel Center, Dallas TX',
    'purchased_at' => now()->toIso8601String(),
]);
```

---

IFTA Reports
------------

[](#ifta-reports)

### Generate IFTA Report

[](#generate-ifta-report)

```
$report = Motive::iftaReports()->generate([
    'quarter' => 4,
    'year' => 2024,
]);

foreach ($report->jurisdictions as $jurisdiction) {
    echo $jurisdiction->state;
    echo $jurisdiction->miles . ' miles';
    echo $jurisdiction->gallons . ' gallons';
    echo $jurisdiction->mpg . ' MPG';
}
```

---

Driver Performance &amp; Safety
-------------------------------

[](#driver-performance--safety)

### List Driver Performance Events

[](#list-driver-performance-events)

```
$events = Motive::driverPerformanceEvents()->list([
    'driver_id' => 123,
    'start_date' => now()->subDays(30)->toDateString(),
    'event_types' => ['harsh_braking', 'speeding', 'rapid_acceleration'],
]);

foreach ($events as $event) {
    echo $event->type;
    echo $event->severity; // 'low', 'medium', 'high'
    echo "Speed: {$event->speed} mph";
    echo "Location: ({$event->latitude}, {$event->longitude})";
    echo $event->occurredAt->format('Y-m-d H:i');
}
```

### Get Driver Scorecard

[](#get-driver-scorecard)

```
$scorecard = Motive::scorecard()->forDriver(123, [
    'start_date' => now()->subDays(30)->toDateString(),
    'end_date' => now()->toDateString(),
]);

echo "Overall Score: {$scorecard->overallScore}";
echo "Harsh Braking: {$scorecard->harshBrakingScore}";
echo "Speeding: {$scorecard->speedingScore}";
echo "Miles Driven: {$scorecard->totalMiles}";
```

---

Webhooks
--------

[](#webhooks)

### Register a Webhook

[](#register-a-webhook)

```
use Motive\Enums\WebhookEvent;

$webhook = Motive::webhooks()->create([
    'url' => 'https://your-app.com/webhooks/motive',
    'events' => [
        WebhookEvent::VehicleLocationUpdated->value,
        WebhookEvent::HosViolationDetected->value,
        WebhookEvent::DispatchStatusChanged->value,
    ],
    'secret' => 'your-webhook-secret',
]);
```

### List Webhooks

[](#list-webhooks)

```
$webhooks = Motive::webhooks()->list();

foreach ($webhooks as $webhook) {
    echo $webhook->url;
    echo implode(', ', $webhook->events);
    echo $webhook->status; // 'active', 'inactive'
}
```

### Handle Incoming Webhooks

[](#handle-incoming-webhooks)

Register the webhook route with signature verification middleware:

```
// routes/web.php
use Motive\Http\Middleware\VerifyWebhookSignature;

Route::post('/webhooks/motive', [WebhookController::class, 'handle'])
    ->middleware(VerifyWebhookSignature::class);
```

Process webhook payloads:

```
use Motive\Webhooks\WebhookPayload;
use Motive\Enums\WebhookEvent;

class WebhookController extends Controller
{
    public function handle(Request $request)
    {
        $payload = WebhookPayload::fromRequest($request);

        match ($payload->event) {
            WebhookEvent::VehicleLocationUpdated => $this->handleVehicleLocation($payload),
            WebhookEvent::HosViolationDetected => $this->handleHosViolation($payload),
            WebhookEvent::DispatchStatusChanged => $this->handleDispatchStatus($payload),
            default => null,
        };

        return response()->json(['received' => true]);
    }

    protected function handleVehicleLocation(WebhookPayload $payload): void
    {
        $vehicleId = $payload->data['vehicle_id'];
        $latitude = $payload->data['latitude'];
        $longitude = $payload->data['longitude'];

        // Update your database, broadcast to websockets, etc.
    }
}
```

---

OAuth Authentication
--------------------

[](#oauth-authentication)

### Generate Authorization URL

[](#generate-authorization-url)

```
use Motive\Enums\Scope;

$url = Motive::oauth()->authorizationUrl(
    scopes: [
        Scope::VehiclesRead,
        Scope::UsersRead,
        Scope::HosRead,
    ],
    state: 'random-state-string',
);

return redirect($url);
```

### Exchange Code for Tokens

[](#exchange-code-for-tokens)

```
// In your callback controller
$tokens = Motive::oauth()->exchangeCode($request->code);

// Store tokens securely
$user->update([
    'motive_access_token' => $tokens->accessToken,
    'motive_refresh_token' => $tokens->refreshToken,
    'motive_token_expires_at' => $tokens->expiresAt,
]);
```

### Use OAuth Tokens

[](#use-oauth-tokens)

```
// With stored tokens
Motive::withOAuth(
    accessToken: $user->motive_access_token,
    refreshToken: $user->motive_refresh_token,
    expiresAt: $user->motive_token_expires_at,
)->vehicles()->list();
```

### Refresh Tokens

[](#refresh-tokens)

```
$newTokens = Motive::oauth()->refreshToken($user->motive_refresh_token);

$user->update([
    'motive_access_token' => $newTokens->accessToken,
    'motive_refresh_token' => $newTokens->refreshToken,
    'motive_token_expires_at' => $newTokens->expiresAt,
]);
```

### Implementing a Custom Token Store

[](#implementing-a-custom-token-store)

```
use Motive\Contracts\TokenStore;

class DatabaseTokenStore implements TokenStore
{
    public function __construct(
        protected User $user
    ) {}

    public function getAccessToken(): ?string
    {
        return $this->user->motive_access_token;
    }

    public function getRefreshToken(): ?string
    {
        return $this->user->motive_refresh_token;
    }

    public function getExpiresAt(): ?CarbonInterface
    {
        return $this->user->motive_token_expires_at;
    }

    public function store(string $accessToken, string $refreshToken, CarbonInterface $expiresAt): void
    {
        $this->user->update([
            'motive_access_token' => $accessToken,
            'motive_refresh_token' => $refreshToken,
            'motive_token_expires_at' => $expiresAt,
        ]);
    }
}

// Usage
Motive::withTokenStore(new DatabaseTokenStore($user))->vehicles()->list();
```

---

Multi-Tenancy
-------------

[](#multi-tenancy)

### Configure Multiple Connections

[](#configure-multiple-connections)

```
// config/motive.php
return [
    'default' => 'default',

    'connections' => [
        'default' => [
            'auth_driver' => 'api_key',
            'api_key' => env('MOTIVE_API_KEY'),
        ],
        'company-a' => [
            'auth_driver' => 'api_key',
            'api_key' => env('MOTIVE_COMPANY_A_API_KEY'),
        ],
        'company-b' => [
            'auth_driver' => 'api_key',
            'api_key' => env('MOTIVE_COMPANY_B_API_KEY'),
        ],
    ],
];
```

### Use Different Connections

[](#use-different-connections)

```
// Use default connection
$vehicles = Motive::vehicles()->list();

// Use specific connection
$vehiclesA = Motive::connection('company-a')->vehicles()->list();
$vehiclesB = Motive::connection('company-b')->vehicles()->list();
```

### Dynamic Authentication

[](#dynamic-authentication)

```
// Dynamically set API key at runtime
$vehicles = Motive::withApiKey($tenant->motive_api_key)
    ->vehicles()
    ->list();

// Or with OAuth tokens
$vehicles = Motive::withOAuth($tenant->access_token, $tenant->refresh_token)
    ->vehicles()
    ->list();
```

---

Context Modifiers
-----------------

[](#context-modifiers)

### Set Timezone

[](#set-timezone)

```
// All datetime values will be converted to this timezone
$logs = Motive::withTimezone('America/Chicago')
    ->hosLogs()
    ->list();
```

### Use Metric Units

[](#use-metric-units)

```
// Distances in kilometers, volumes in liters
$vehicles = Motive::withMetricUnits()
    ->vehicles()
    ->list();
```

### Acting as a User

[](#acting-as-a-user)

```
// Set X-User-Id header for audit trails
$dispatch = Motive::withUserId($currentUser->id)
    ->dispatches()
    ->create([...]);
```

### Chaining Modifiers

[](#chaining-modifiers)

```
$vehicles = Motive::connection('company-a')
    ->withTimezone('America/Los_Angeles')
    ->withMetricUnits()
    ->withUserId($user->id)
    ->vehicles()
    ->list();
```

---

Error Handling
--------------

[](#error-handling)

### Exception Types

[](#exception-types)

```
use Motive\Exceptions\MotiveException;
use Motive\Exceptions\AuthenticationException;
use Motive\Exceptions\AuthorizationException;
use Motive\Exceptions\ValidationException;
use Motive\Exceptions\NotFoundException;
use Motive\Exceptions\RateLimitException;
use Motive\Exceptions\ServerException;

try {
    $vehicle = Motive::vehicles()->find(123);
} catch (NotFoundException $e) {
    // Vehicle not found (404)
    echo "Vehicle not found";
} catch (AuthenticationException $e) {
    // Invalid API key or expired token (401)
    echo "Authentication failed: " . $e->getMessage();
} catch (AuthorizationException $e) {
    // Insufficient permissions (403)
    echo "Not authorized: " . $e->getMessage();
} catch (ValidationException $e) {
    // Invalid request data (422)
    foreach ($e->errors() as $field => $messages) {
        echo "{$field}: " . implode(', ', $messages);
    }
} catch (RateLimitException $e) {
    // Too many requests (429)
    echo "Rate limited. Retry after: " . $e->retryAfter() . " seconds";
} catch (ServerException $e) {
    // Motive server error (5xx)
    echo "Server error: " . $e->getMessage();
} catch (MotiveException $e) {
    // Any other Motive API error
    echo "Error: " . $e->getMessage();
}
```

### Accessing Response Details

[](#accessing-response-details)

```
try {
    $vehicle = Motive::vehicles()->create([...]);
} catch (MotiveException $e) {
    $statusCode = $e->getCode();
    $response = $e->getResponse(); // Full response object
    $body = $e->getResponseBody(); // Decoded JSON body
}
```

---

Testing
-------

[](#testing)

### Faking the Motive Client

[](#faking-the-motive-client)

```
use Motive\Facades\Motive;
use Motive\Data\Vehicle;

public function test_it_syncs_vehicles(): void
{
    Motive::fake([
        'vehicles' => [
            Vehicle::factory()->make(['number' => 'TRUCK-001']),
            Vehicle::factory()->make(['number' => 'TRUCK-002']),
        ],
    ]);

    $this->artisan('sync:vehicles');

    Motive::assertRequested('vehicles.list');
    Motive::assertRequestCount(1);

    $this->assertDatabaseHas('vehicles', ['number' => 'TRUCK-001']);
    $this->assertDatabaseHas('vehicles', ['number' => 'TRUCK-002']);
}
```

### Asserting Specific Requests

[](#asserting-specific-requests)

```
Motive::fake();

// Your code that makes API calls
$vehicle = Motive::vehicles()->create([
    'number' => 'NEW-TRUCK',
]);

// Assert the request was made with specific data
Motive::assertRequested('vehicles.create', function ($request) {
    return $request->data('number') === 'NEW-TRUCK';
});
```

### Using Factories

[](#using-factories)

```
use Motive\Data\Vehicle;
use Motive\Data\User;
use Motive\Data\HosLog;

// Create a single instance
$vehicle = Vehicle::factory()->make();

// Create multiple instances
$vehicles = Vehicle::factory()->count(5)->make();

// With specific attributes
$vehicle = Vehicle::factory()->make([
    'number' => 'CUSTOM-001',
    'status' => VehicleStatus::Active,
]);

// With states
$vehicle = Vehicle::factory()
    ->active()
    ->withCurrentDriver()
    ->make();

// Nested relationships
$user = User::factory()
    ->asDriver()
    ->withVehicle()
    ->make();
```

### Fake Specific Responses

[](#fake-specific-responses)

```
use Motive\Testing\FakeResponse;

Motive::fake([
    'vehicles.list' => FakeResponse::paginated([
        Vehicle::factory()->make(),
    ], total: 100, perPage: 25),

    'vehicles.find' => FakeResponse::json([
        'vehicle' => Vehicle::factory()->make(['id' => 123]),
    ]),

    'vehicles.create' => FakeResponse::error(422, [
        'errors' => ['number' => ['Vehicle number already exists']],
    ]),
]);
```

---

Advanced Usage
--------------

[](#advanced-usage)

### Raw API Requests

[](#raw-api-requests)

```
// Make a raw GET request
$response = Motive::get('/v1/custom_endpoint', [
    'param' => 'value',
]);

// Make a raw POST request
$response = Motive::post('/v1/custom_endpoint', [
    'field' => 'value',
]);

// Access response data
$data = $response->json();
$status = $response->status();
$headers = $response->headers();
```

### Macro Extensions

[](#macro-extensions)

```
// In a service provider
use Motive\Resources\VehiclesResource;

VehiclesResource::macro('findByLicensePlate', function (string $plate) {
    return $this->list(['license_plate_number' => $plate])->first();
});

// Usage
$vehicle = Motive::vehicles()->findByLicensePlate('ABC1234');
```

### Custom HTTP Client Options

[](#custom-http-client-options)

```
// Temporary timeout override
$vehicles = Motive::withOptions(['timeout' => 60])
    ->vehicles()
    ->list();
```

---

Available Resources
-------------------

[](#available-resources)

ResourceMethods`vehicles()`list, paginate, find, findByNumber, findByExternalId, create, update, delete, currentLocation, locations`users()`list, paginate, find, findByExternalId, create, update, delete, deactivate, reactivate`assets()`list, paginate, find, create, update, delete, assignToVehicle, unassignFromVehicle`hosLogs()`list, paginate, find, create, update, delete, certify`hosAvailability()`list, forDriver`hosViolations()`list`dispatches()`list, paginate, find, create, update, delete`dispatchLocations()`list, find, create, update, delete`locations()`list, paginate, find, create, update, delete, findNearest`geofences()`list, paginate, find, create, update, delete`groups()`list, paginate, find, create, update, delete, addMember, removeMember`messages()`list, paginate, find, send, broadcast`documents()`list, paginate, find, upload, download, delete, updateStatus`inspectionReports()`list, paginate, find, downloadPdf`fuelPurchases()`list, paginate, find, create, update, delete`driverPerformanceEvents()`list, paginate, find`iftaReports()`generate, list`forms()`list`formEntries()`list, find`timecards()`list, paginate, find, update`utilization()`forVehicle, forFleet, daily, summary`scorecard()`forDriver, forFleet`webhooks()`list, find, create, update, delete, test, logs`companies()`current`faultCodes()`list`externalIds()`set, get, delete`freightVisibility()`shipments, tracking, eta`motiveCard()`list, transactions, limits`cameraConnections()`list`cameraControl()`requestVideo, getVideo`drivingPeriods()`list, current, history`vehicleGateways()`list`reeferActivity()`list, forVehicle---

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

###  Health Score

35

—

LowBetter than 80% of packages

Maintenance78

Regular maintenance activity

Popularity15

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity34

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

120d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/44d0a5db4aa44c040bcdd2d07a5a4e894f02fc08a11140e2282ac7d080d113f6?d=identicon)[erikgall](/maintainers/erikgall)

---

Top Contributors

[![erikgall](https://avatars.githubusercontent.com/u/8866568?v=4)](https://github.com/erikgall "erikgall (33 commits)")

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/erikgall-motive-sdk/health.svg)

```
[![Health](https://phpackages.com/badges/erikgall-motive-sdk/health.svg)](https://phpackages.com/packages/erikgall-motive-sdk)
```

###  Alternatives

[spatie/laravel-query-builder

Easily build Eloquent queries from API requests

4.4k26.9M220](/packages/spatie-laravel-query-builder)[essa/api-tool-kit

set of tools to build an api with laravel

52680.5k](/packages/essa-api-tool-kit)[esign/laravel-conversions-api

A laravel wrapper package around the Facebook Conversions API

69145.4k](/packages/esign-laravel-conversions-api)[specialtactics/l5-api

Dependencies for the Laravel API Boilerplate package

3672.8k2](/packages/specialtactics-l5-api)[surface/laravel-webfinger

A Laravel package to create an ActivityPub webfinger.

113.8k](/packages/surface-laravel-webfinger)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
