PHPackages                             stitch-digital/simpro-php-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. stitch-digital/simpro-php-sdk

ActiveLibrary[API Development](/categories/api)

stitch-digital/simpro-php-sdk
=============================

An SDK to easily work with the Simpro API

1.1.16(yesterday)17351MITPHPPHP ^8.2

Since Jan 27Pushed 6d agoCompare

[ Source](https://github.com/stitch-digital/simpro-php-sdk)[ Packagist](https://packagist.org/packages/stitch-digital/simpro-php-sdk)[ Docs](https://github.com/stitch-digital/simpro-php-sdk)[ RSS](/packages/stitch-digital-simpro-php-sdk/feed)WikiDiscussions main Synced today

READMEChangelog (10)Dependencies (55)Versions (78)Used By (0)

Simpro PHP SDK
==============

[](#simpro-php-sdk)

[![Latest Version on Packagist](https://camo.githubusercontent.com/478038b108090c2cb0ee21f4514f3c88cd01fcbb07642485620d75375be54d47/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f7374697463682d6469676974616c2f73696d70726f2d7068702d73646b2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/stitch-digital/simpro-php-sdk)[![Total Downloads](https://camo.githubusercontent.com/fe2fb36617250e28e6b91768f29fefb970ec2d08510ecfc6bf44e10f5690cc28/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f7374697463682d6469676974616c2f73696d70726f2d7068702d73646b2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/stitch-digital/simpro-php-sdk)

This package is an unofficial PHP SDK for the Simpro API, built with [Saloon](https://docs.saloon.dev/).

⚠️ **Active Development Notice**

This package is under active development. The API, features, and internal structure may change at any time.

A stable, production-ready release has **not** yet been tagged. Until a `v1.0.0` (or similar) release is published, this SDK should be considered **experimental** and used with caution in production environments.

 [ ![Simpro Logo](simpro-logo.jpg) ](https://www.simprogroup.com)

About Simpro
------------

[](#about-simpro)

[Simpro](https://www.simprogroup.com/) is a cloud-based field service management platform designed for trade and service contractors, supporting industries such as fire protection, electrical, plumbing, HVAC, and security. It helps businesses manage jobs, quoting, scheduling, asset registers, compliance, and invoicing in one central system, replacing spreadsheets and disconnected tools with a mobile-friendly platform where technicians can capture job data, photos, and service results on site while office teams manage workflows, customer communication, and reporting, with integrations into accounting and other business software.

- **Website:**
- **GitHub:**

Table of Contents
-----------------

[](#table-of-contents)

- [Installation](#installation)
- [Quick Start](#quick-start)
- [Authentication Methods](#authentication-methods)
    - [OAuth (Authorization Code Grant)](#oauth-authorization-code-grant)
    - [API Key](#api-key)
- [Usage](#usage)
    - [Setting a Timeout](#setting-a-timeout)
    - [Rate Limiting](#rate-limiting)
    - [Caching](#caching)
    - [Laravel Integration](#laravel-integration)
    - [Handling Errors](#handling-errors)
- [Resources](#resources)
- [Pagination and Querying](#pagination-and-querying)
    - [Fluent Search API](#fluent-search-api)
    - [Search Methods](#search-methods)
    - [Using Laravel Collections](#using-laravel-collections)
- [Security](#security)
- [Credits](#credits)
- [License](#license)

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

[](#installation)

```
composer require stitch-digital/simpro-php-sdk
```

Quick Start
-----------

[](#quick-start)

Choose the authentication method that fits your use case. See the [Authentication Methods](#authentication-methods) section below for details.

```
use Simpro\PhpSdk\Simpro\Connectors\SimproApiKeyConnector;

// For server-to-server integrations
$connector = new SimproApiKeyConnector(
    baseUrl: 'https://example.simprosuite.com/api/v1.0',
    apiKey: 'your-api-key'
);

// Get information about your Simpro instance
$version = $connector->info()->version(); // Returns: '99.0.0.0.1.1'

// List all companies
$companies = $connector->companies()->list();
foreach ($companies->items() as $company) {
    echo "{$company->id}: {$company->name}\n";
}
```

Behind the scenes, the SDK uses [Saloon](https://docs.saloon.dev/) v3 to make HTTP requests.

**Note:** This SDK is in early development. API resources and endpoints are being actively developed.

Authentication Methods
----------------------

[](#authentication-methods)

This SDL supports two authentication methods. Choose the one that best fits your use case:

### OAuth (Authorization Code Grant)

[](#oauth-authorization-code-grant)

**Best for:** Web applications where users can be redirected to Simpro's authorization page.

This flow requires users to approve access to your application. After approval, they're redirected back with a code you exchange for an access token.

```
use Simpro\PhpSdk\Simpro\Connectors\SimproOAuthConnector;

// Create the connector
$connector = new SimproOAuthConnector(
    baseUrl: 'https://example.simprosuite.com',
    clientId: 'your-client-id',
    clientSecret: 'your-client-secret',
    redirectUri: 'https://yourapp.com/oauth/callback'
);

// Step 1: Redirect user to authorization URL
$authUrl = $connector->getAuthorizationUrl();
// Redirect user to $authUrl

// Step 2: In your callback handler, exchange the code for a token
$code = $_GET['code']; // From the callback URL
$authenticator = $connector->getAccessToken($code);

// Step 3: Authenticate your connector and store the authenticator
$connector->authenticate($authenticator);

// Store $authenticator->serialize() securely (encrypted in database)
// so you can reuse it later

// Step 4: Make API requests
$version = $connector->info()->version();

// Step 5: Check for expired tokens and refresh when needed
if ($authenticator->hasExpired()) {
    $newAuthenticator = $connector->refreshAccessToken($authenticator);
    // Update stored authenticator
}
```

**Token Management:**

The `AccessTokenAuthenticator` returned by `getAccessToken()` contains:

- Access token
- Refresh token
- Expiry timestamp

You should securely store this (encrypted) in your database and check for expiration before each request.

```
// Serializing for storage
$serialized = $authenticator->serialize();

// Unserializing when retrieving
use Saloon\Http\Auth\AccessTokenAuthenticator;
$authenticator = AccessTokenAuthenticator::unserialize($serialized);
```

### API Key

[](#api-key)

**Best for:** Server-to-server integrations, background jobs, and command-line tools.

This is the simplest authentication method - just provide your API key and start making requests.

```
use Simpro\PhpSdk\Simpro\Connectors\SimproApiKeyConnector;

// Create the connector
$connector = new SimproApiKeyConnector(
    baseUrl: 'https://example.simprosuite.com/api/v1.0',
    apiKey: 'your-api-key'
);

// Make API requests - authentication is handled automatically
$version = $connector->info()->version();
```

**That's it!** No token management, no refresh logic - just simple, straightforward authentication.

Usage
-----

[](#usage)

### Authentication

[](#authentication)

The SDK supports two authentication methods. See the [Authentication Methods](#authentication-methods) section above for detailed information:

- **OAuth Authorization Code Grant**: For web applications with user redirects
- **API Key**: For server-to-server integrations

### Setting a timeout

[](#setting-a-timeout)

By default, the SDK waits 10 seconds for a response. Override via the constructor:

```
// OAuth
$connector = new SimproOAuthConnector(
    baseUrl: 'https://example.simprosuite.com',
    clientId: 'your-client-id',
    clientSecret: 'your-client-secret',
    redirectUri: 'https://yourapp.com/oauth/callback',
    scopes: [],
    requestTimeout: 30
);

// API Key
$connector = new SimproApiKeyConnector(
    baseUrl: 'https://example.simprosuite.com/api/v1.0',
    apiKey: 'your-api-key',
    requestTimeout: 30
);
```

### Rate Limiting

[](#rate-limiting)

The SDK automatically handles Simpro's API rate limit of **10 requests per second** per base URL. By default, when the limit is reached, the SDK waits and retries automatically.

See [Simpro's API documentation](https://developer.simprogroup.com/apidoc/?page=ed8457e003ba0f6197756eca5a61fde9) for more details on rate limits.

#### Default Behavior

[](#default-behavior)

Rate limiting is enabled by default with sensible settings:

```
// No configuration needed - rate limiting works out of the box
$connector = new SimproApiKeyConnector(
    baseUrl: 'https://example.simprosuite.com/api/v1.0',
    apiKey: 'your-api-key'
);
```

With default settings:

- **10 requests per second** limit
- **Sleep and retry** when limit is reached (no exceptions)
- **Memory store** for tracking request counts (per-process)

#### Custom Store

[](#custom-store)

For applications running multiple processes or workers, use a persistent store to share rate limit state:

```
use Saloon\RateLimitPlugin\Stores\FileStore;
use Simpro\PhpSdk\Simpro\RateLimit\RateLimitConfig;

// File-based store for persistence across processes
$connector = new SimproApiKeyConnector(
    baseUrl: 'https://example.simprosuite.com/api/v1.0',
    apiKey: 'your-api-key',
    rateLimitConfig: new RateLimitConfig(
        store: new FileStore('/var/cache/simpro'),
    ),
);
```

For Laravel applications, you can use a cache-based store:

```
use Saloon\RateLimitPlugin\Stores\LaravelCacheStore;
use Simpro\PhpSdk\Simpro\RateLimit\RateLimitConfig;

// Use Laravel's Redis cache for distributed rate limiting
$connector = new SimproApiKeyConnector(
    baseUrl: 'https://example.simprosuite.com/api/v1.0',
    apiKey: 'your-api-key',
    rateLimitConfig: new RateLimitConfig(
        store: new LaravelCacheStore(Cache::store('redis')),
    ),
);
```

See [Saloon's Rate Limit Plugin documentation](https://docs.saloon.dev/installable-plugins/handling-rate-limits) for all available stores.

#### Throwing Exceptions

[](#throwing-exceptions)

For queue jobs or situations where you want to handle rate limits yourself, configure the SDK to throw exceptions instead of sleeping:

```
use Saloon\RateLimitPlugin\Exceptions\RateLimitReachedException;
use Simpro\PhpSdk\Simpro\RateLimit\RateLimitConfig;

$connector = new SimproApiKeyConnector(
    baseUrl: 'https://example.simprosuite.com/api/v1.0',
    apiKey: 'your-api-key',
    rateLimitConfig: RateLimitConfig::throwing(),
);

try {
    $response = $connector->send($request);
} catch (RateLimitReachedException $e) {
    // Release job back to queue with delay
    $secondsToWait = $e->getLimit()->getRemainingSeconds();
    // ... release job with $secondsToWait delay
}
```

#### Disabling Rate Limiting

[](#disabling-rate-limiting)

If you need to disable rate limiting entirely:

```
$connector = new SimproApiKeyConnector(
    baseUrl: 'https://example.simprosuite.com/api/v1.0',
    apiKey: 'your-api-key'
);

$connector->useRateLimitPlugin(false);
```

### Caching

[](#caching)

The SDK supports response caching using Saloon's cache plugin. Caching is **opt-in** and disabled by default - you must provide a `CacheConfig` to enable it.

#### Enabling Caching

[](#enabling-caching)

You can enable caching using any PSR-16 compatible cache, Laravel's cache system, or Flysystem:

```
use Simpro\PhpSdk\Simpro\Cache\CacheConfig;

// Using PSR-16 cache (e.g., Symfony Cache)
$connector = new SimproApiKeyConnector(
    baseUrl: 'https://example.simprosuite.com/api/v1.0',
    apiKey: 'your-api-key',
    cacheConfig: CacheConfig::psr16($symfonyCache),
);

// Using Laravel cache
$connector = new SimproApiKeyConnector(
    baseUrl: 'https://example.simprosuite.com/api/v1.0',
    apiKey: 'your-api-key',
    cacheConfig: CacheConfig::laravel(Cache::store('redis')),
);

// Using Flysystem
$connector = new SimproApiKeyConnector(
    baseUrl: 'https://example.simprosuite.com/api/v1.0',
    apiKey: 'your-api-key',
    cacheConfig: CacheConfig::flysystem($filesystem),
);
```

#### Cache Options

[](#cache-options)

Configure cache expiry and key prefix:

```
use Simpro\PhpSdk\Simpro\Cache\CacheConfig;

$connector = new SimproApiKeyConnector(
    baseUrl: 'https://example.simprosuite.com/api/v1.0',
    apiKey: 'your-api-key',
    cacheConfig: CacheConfig::laravel(
        cache: Cache::store('redis'),
        expiryInSeconds: 600,  // 10 minutes (default: 300)
        keyPrefix: 'my-app',   // Optional additional prefix
    ),
);
```

#### Cache Behavior

[](#cache-behavior)

AspectBehaviorDefault stateDisabled (opt-in)Cached methodsGET and OPTIONS onlyCached responsesSuccessful responses onlyCache key prefix`simpro:{hostname}[:userPrefix]:{hash}`Default expiry300 seconds (5 minutes)Cache keys are automatically prefixed with the Simpro instance hostname to ensure multi-tenant isolation.

#### Enabling Caching After Construction

[](#enabling-caching-after-construction)

You can also enable or disable caching after creating the connector:

```
use Simpro\PhpSdk\Simpro\Cache\CacheConfig;

$connector = new SimproApiKeyConnector(
    baseUrl: 'https://example.simprosuite.com/api/v1.0',
    apiKey: 'your-api-key',
);

// Enable caching later
$connector->setCacheConfig(CacheConfig::laravel(Cache::store('redis')));

// Check if caching is enabled
if ($connector->hasCaching()) {
    // ...
}

// Disable caching
$connector->setCacheConfig(null);
```

#### Checking Cache Status

[](#checking-cache-status)

After sending a request, you can check if the response came from cache:

```
$response = $connector->send($request);

if ($response->isCached()) {
    // Response was served from cache
}
```

#### Disabling Cache for Specific Requests

[](#disabling-cache-for-specific-requests)

You can disable caching for individual requests:

```
$request->disableCaching();
$response = $connector->send($request);
```

#### Invalidating Cache

[](#invalidating-cache)

To invalidate the cache for a specific request:

```
$request->invalidateCache();
$response = $connector->send($request); // Fresh response, also updates cache
```

See [Saloon's Cache Plugin documentation](https://docs.saloon.dev/installable-plugins/caching-responses) for more details.

### Laravel Integration

[](#laravel-integration)

This section provides guidance for integrating the SDK into Laravel applications. The approach differs depending on whether you're building a single-tenant application (API Key) or a multi-tenant application (OAuth).

#### API Key Connector (Single-Tenant Applications)

[](#api-key-connector-single-tenant-applications)

For applications that connect to a single Simpro instance using an API key.

**Configuration** (`config/services.php`):

```
'simpro' => [
    'base_url' => env('SIMPRO_BASE_URL'),
    'api_key' => env('SIMPRO_API_KEY'),
],
```

**Service Provider Binding** (`app/Providers/AppServiceProvider.php`):

```
use Illuminate\Support\Facades\Cache;
use Saloon\RateLimitPlugin\Stores\LaravelCacheStore;
use Simpro\PhpSdk\Simpro\Cache\CacheConfig;
use Simpro\PhpSdk\Simpro\Connectors\SimproApiKeyConnector;
use Simpro\PhpSdk\Simpro\RateLimit\RateLimitConfig;

public function register(): void
{
    $this->app->singleton(SimproApiKeyConnector::class, function ($app) {
        return new SimproApiKeyConnector(
            baseUrl: config('services.simpro.base_url'),
            apiKey: config('services.simpro.api_key'),
            requestTimeout: 30,
            rateLimitConfig: new RateLimitConfig(
                store: new LaravelCacheStore($app['cache']->store()),
            ),
            cacheConfig: CacheConfig::laravel(
                cache: Cache::store('redis'),
                expiryInSeconds: 300,
            ),
        );
    });
}
```

**Usage via Dependency Injection**:

```
use Simpro\PhpSdk\Simpro\Connectors\SimproApiKeyConnector;

class JobController extends Controller
{
    public function __construct(
        private SimproApiKeyConnector $simpro
    ) {}

    public function index()
    {
        $jobs = $this->simpro->jobs()->list()->all();

        return view('jobs.index', compact('jobs'));
    }
}
```

**Optional Facade**:

Create a facade for convenient static access:

```
// app/Facades/Simpro.php
namespace App\Facades;

use Illuminate\Support\Facades\Facade;
use Simpro\PhpSdk\Simpro\Connectors\SimproApiKeyConnector;

/**
 * @method static \Simpro\PhpSdk\Simpro\Resources\InfoResource info()
 * @method static \Simpro\PhpSdk\Simpro\Resources\CompanyResource companies()
 * @method static \Simpro\PhpSdk\Simpro\Resources\JobResource jobs()
 * @method static \Simpro\PhpSdk\Simpro\Resources\CustomerResource customers()
 *
 * @see SimproApiKeyConnector
 */
class Simpro extends Facade
{
    protected static function getFacadeAccessor(): string
    {
        return SimproApiKeyConnector::class;
    }
}
```

Usage:

```
use App\Facades\Simpro;

$version = Simpro::info()->version();
$companies = Simpro::companies()->list()->all();
```

#### OAuth Connector (Multi-Tenant Applications)

[](#oauth-connector-multi-tenant-applications)

For applications where multiple users connect their own Simpro accounts. This approach requires the Saloon Laravel plugin for the Eloquent cast:

```
composer require saloonphp/laravel-plugin "^3.0"
```

**Configuration** (`config/services.php`):

```
'simpro' => [
    'client_id' => env('SIMPRO_CLIENT_ID'),
    'client_secret' => env('SIMPRO_CLIENT_SECRET'),
    'redirect_uri' => env('SIMPRO_REDIRECT_URI'),
],
```

**Model** (`app/Models/SimproConnection.php`):

Store the OAuth authenticator and tenant-specific Simpro URL:

```
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Saloon\Laravel\Casts\EncryptedOAuthAuthenticatorCast;

class SimproConnection extends Model
{
    protected $fillable = [
        'user_id',
        'build_url',
        'simpro_auth',
    ];

    protected function casts(): array
    {
        return [
            'simpro_auth' => EncryptedOAuthAuthenticatorCast::class,
        ];
    }

    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }
}
```

Migration:

```
Schema::create('simpro_connections', function (Blueprint $table) {
    $table->id();
    $table->foreignId('user_id')->constrained()->cascadeOnDelete();
    $table->string('build_url'); // e.g., https://tenant.simprosuite.com
    $table->text('simpro_auth'); // Encrypted OAuth authenticator
    $table->timestamps();
});
```

**Factory** (`app/Services/SimproConnectorFactory.php`):

```
namespace App\Services;

use App\Models\SimproConnection;
use Illuminate\Support\Facades\Cache;
use Saloon\RateLimitPlugin\Stores\LaravelCacheStore;
use Simpro\PhpSdk\Simpro\Cache\CacheConfig;
use Simpro\PhpSdk\Simpro\Connectors\SimproOAuthConnector;
use Simpro\PhpSdk\Simpro\RateLimit\RateLimitConfig;

class SimproConnectorFactory
{
    /**
     * Create an unauthenticated connector for the OAuth flow.
     */
    public function make(string $buildUrl): SimproOAuthConnector
    {
        return new SimproOAuthConnector(
            baseUrl: $buildUrl,
            clientId: config('services.simpro.client_id'),
            clientSecret: config('services.simpro.client_secret'),
            redirectUri: config('services.simpro.redirect_uri'),
            scopes: [],
            requestTimeout: 30,
            rateLimitConfig: new RateLimitConfig(
                store: new LaravelCacheStore(Cache::store()),
            ),
            cacheConfig: CacheConfig::laravel(
                cache: Cache::store('redis'),
                expiryInSeconds: 300,
            ),
        );
    }

    /**
     * Create an authenticated connector from a stored connection.
     */
    public function authenticated(SimproConnection $connection): SimproOAuthConnector
    {
        $connector = $this->make($connection->build_url);
        $authenticator = $connection->simpro_auth;

        // Refresh the token if expired
        if ($authenticator->hasExpired()) {
            $authenticator = $connector->refreshAccessToken($authenticator);
            $connection->update(['simpro_auth' => $authenticator]);
        }

        $connector->authenticate($authenticator);

        return $connector;
    }
}
```

**Controller** (`app/Http/Controllers/SimproOAuthController.php`):

```
namespace App\Http\Controllers;

use App\Models\SimproConnection;
use App\Services\SimproConnectorFactory;
use Illuminate\Http\Request;

class SimproOAuthController extends Controller
{
    public function __construct(
        private SimproConnectorFactory $factory
    ) {}

    public function redirect(Request $request)
    {
        $request->validate(['build_url' => 'required|url']);

        // Store the build URL in session for the callback
        session(['simpro_build_url' => $request->build_url]);

        $connector = $this->factory->make($request->build_url);

        return redirect($connector->getAuthorizationUrl());
    }

    public function callback(Request $request)
    {
        $buildUrl = session('simpro_build_url');
        $connector = $this->factory->make($buildUrl);

        $authenticator = $connector->getAccessToken($request->code);

        SimproConnection::updateOrCreate(
            ['user_id' => $request->user()->id],
            [
                'build_url' => $buildUrl,
                'simpro_auth' => $authenticator,
            ]
        );

        return redirect()->route('dashboard')
            ->with('success', 'Simpro account connected successfully.');
    }
}
```

**Usage Example**:

```
use App\Models\SimproConnection;
use App\Services\SimproConnectorFactory;

class SyncJobsCommand extends Command
{
    public function handle(SimproConnectorFactory $factory): void
    {
        $connections = SimproConnection::all();

        foreach ($connections as $connection) {
            $simpro = $factory->authenticated($connection);

            foreach ($simpro->jobs()->list()->items() as $job) {
                // Process each job...
            }
        }
    }
}
```

#### Further Reading

[](#further-reading)

- [Saloon Laravel Integration](https://docs.saloon.dev/installable-plugins/laravel-integration)
- [Saloon OAuth2 Authentication](https://docs.saloon.dev/digging-deeper/oauth2-authentication/oauth2-authentication)
- [Understanding Simpro Grant Types](https://developer.simprogroup.com/apidoc/?page=b0d64c044a432c8e0f9f01e0641d5596)

### Handling Errors

[](#handling-errors)

The SDK uses Saloon's `AlwaysThrowOnErrors` trait on the connector, which means exceptions will automatically be thrown whenever a request fails (4xx or 5xx response status codes). You don't need to manually check if a request failed or call `throw()` on responses - exceptions are thrown automatically.

Saloon's built-in exceptions are used for most errors, with a custom exception for validation errors (422 status codes).

#### Exception Hierarchy

[](#exception-hierarchy)

The SDK uses Saloon's exception hierarchy:

```
SaloonException
├── FatalRequestException (Connection Errors)
└── RequestException (Request Errors)
    ├── ServerException (5xx)
    │   ├── InternalServerErrorException (500)
    │   ├── ServiceUnavailableException (503)
    │   └── GatewayTimeoutException (504)
    └── ClientException (4xx)
        ├── UnauthorizedException (401)
        ├── PaymentRequiredException (402)
        ├── ForbiddenException (403)
        ├── NotFoundException (404)
        ├── MethodNotAllowedException (405)
        ├── RequestTimeOutException (408)
        ├── UnprocessableEntityException (422)
        │   └── ValidationException (422 - Custom)
        └── TooManyRequestsException (429)

```

#### Validation Errors

[](#validation-errors)

For validation errors (422 status code), the SDK throws a custom `ValidationException` which extends Saloon's `UnprocessableEntityException`. This exception provides additional methods to access validation error details:

```
use Saloon\Exceptions\Request\ClientException;
use Saloon\Exceptions\Request\ServerException;
use Simpro\PhpSdk\Simpro\Exceptions\ValidationException;

try {
    // Make an API request
    $response = $connector->send($request);
} catch (ValidationException $exception) {
    // Handle validation errors (422)
    // Get a string describing all errors
    $message = $exception->getMessage();

    // Get all validation errors as an array
    $errors = $exception->getErrors();
    // ['field_name' => ['Error message 1', 'Error message 2']]

    // Get errors for a specific field
    $fieldErrors = $exception->getErrorsForField('field_name');

    // Check if a specific field has errors
    $hasErrors = $exception->hasErrorsForField('field_name');

    // Get all error messages as a flat array
    $allMessages = $exception->getAllErrorMessages();

    // Access the Saloon Response object for debugging
    $response = $exception->getResponse();
} catch (ClientException $exception) {
    // Handle 4xx errors (401, 403, 404, etc.)
    $message = $exception->getMessage();
    $response = $exception->getResponse();
} catch (ServerException $exception) {
    // Handle 5xx errors
    $message = $exception->getMessage();
    $response = $exception->getResponse();
}
```

#### Connection Errors

[](#connection-errors)

If Saloon cannot connect to the API, it will throw a `FatalRequestException`:

```
use Saloon\Exceptions\Request\FatalRequestException;

try {
    $response = $connector->send($request);
} catch (FatalRequestException $exception) {
    // Handle connection errors (network issues, DNS failures, etc.)
    $message = $exception->getMessage();
}
```

Resources
---------

[](#resources)

The SDK provides resource-based APIs for working with different Simpro entities. Each resource has its own documentation page with detailed examples and usage instructions.

### Available Resources

[](#available-resources)

- **[Info](docs/info-resource.md)** - Get information about your Simpro instance, including version, country, and enabled features
- **[Companies](docs/companies-resource.md)** - Access company information and manage multi-company environments
- **[Customer Assets](docs/customer-assets-resource.md)** - List customer assets with site associations and service levels (read-only)
- **[Jobs](docs/jobs-resource.md)** - Manage jobs with full CRUD operations (create, list, get, update, delete)
- **[Customers](docs/customers-resource.md)** - Customer company management with full CRUD operations
- **[Quotes](docs/quotes-resource.md)** - Quote management with full CRUD operations
- **[Invoices](docs/invoices-resource.md)** - Invoice management with full CRUD operations
- **[Schedules](docs/schedules-resource.md)** - View job and activity schedules (read-only)
- **[Activity Schedules](docs/activity-schedules-resource.md)** - Manage scheduled activities for staff members (full CRUD)
- **[Employees](docs/employees-resource.md)** - Employee management with full CRUD operations and nested resources (timesheets, licences, custom fields, attachments)
- **[CurrentUser](docs/current-user-resource.md)** - Get authenticated user information
- **[Reports](docs/reports-resource.md)** - Access job cost-to-complete reports (financial and operations)
- **[Notes](docs/notes-resource.md)** - List customer notes across all customers with basic and detailed views (read-only)
- **[Job Cost Centers](docs/job-cost-centers-resource.md)** - List cost centers across all jobs (read-only)
- **[Job Work Orders](docs/job-work-orders-resource.md)** - List work orders across all jobs with basic and detailed views (read-only)
- **[Setup](docs/setup-resource.md)** - Configure system settings: webhooks, tax codes, payment methods, custom fields, labor rates, and more
- **[Tasks](docs/tasks-resource.md)** - List tasks across all jobs and customers with basic and detailed views (read-only)

More resources will be added as development continues.

Pagination and Querying
-----------------------

[](#pagination-and-querying)

Resource methods that return lists use a `QueryBuilder` instance that provides fluent search, filtering, and ordering capabilities. The query builder wraps Saloon's pagination plugin and handles pagination automatically.

### Basic Usage

[](#basic-usage)

```
// List all companies (returns QueryBuilder)
$companies = $connector->companies()->list();

// Iterate over all items across all pages
foreach ($companies->items() as $company) {
    echo "{$company->id}: {$company->name}\n";
}

// Or get the first result
$first = $connector->companies()->list()->first();

// Or get all results as an array
$all = $connector->companies()->list()->all();
```

### Fluent Search API

[](#fluent-search-api)

The SDK provides a fluent search API for building complex queries:

```
use Simpro\PhpSdk\Simpro\Query\Search;

// Simple wildcard search
$result = $connector->companies()->listDetailed()
    ->search(Search::make()->column('Name')->find('Test'))
    ->first();

// Multiple search criteria with OR logic
$results = $connector->companies()->list()
    ->search([
        Search::make()->column('Name')->find('Corp'),
        Search::make()->column('ID')->greaterThan(5),
    ])
    ->matchAny()
    ->orderByDesc('Name')
    ->collect();

// Alternative where() syntax
$results = $connector->companies()->list()
    ->where('Name', 'like', 'Acme')
    ->where('ID', '>=', 10)
    ->first();
```

### Search Methods

[](#search-methods)

The `Search` class provides these methods:

MethodDescriptionExample Value`equals($value)`Exact match`Test``find($value)`Wildcard search`%25Test%25``startsWith($value)`Starts with`Test%25``endsWith($value)`Ends with`%25Test``lessThan($value)`Less than`lt(10)``lessThanOrEqual($value)`Less than or equal`le(10)``greaterThan($value)`Greater than`gt(10)``greaterThanOrEqual($value)`Greater than or equal`ge(10)``notEqual($value)`Not equal`ne(Cancelled)``between($min, $max)`Range`between(1,100)``in($array)`In list`in(Active,Pending)``notIn($array)`Not in list`!in(Cancelled,Deleted)`### Using Laravel Collections

[](#using-laravel-collections)

The `collect()` method returns a `LazyCollection` for powerful data transformations:

```
$companies = $connector->companies()->list();

$filtered = $companies->collect()
    ->filter(fn($company) => $company->id > 0)
    ->map(fn($company) => ['id' => $company->id, 'name' => $company->name])
    ->sortBy('name');
```

### Array Filters (Backward Compatible)

[](#array-filters-backward-compatible)

You can also pass filters as an array to list methods:

```
$companies = $connector->companies()->list(['Name' => 'Test Company']);
```

### Controlling Page Size

[](#controlling-page-size)

By default, 30 items are fetched per page:

```
$builder = $connector->companies()->list();
$builder->getPaginator()->setPerPageLimit(100);
```

### Pagination Metadata

[](#pagination-metadata)

The Simpro API returns pagination information in response headers:

- `Result-Total`: Total number of results
- `Result-Pages`: Total number of pages

The paginator automatically reads these headers and handles pagination for you.

Security
--------

[](#security)

If you discover any security related issues, please email  instead of using the issue tracker.

Credits
-------

[](#credits)

- [Stitch Digital](https://www.stitch-digital.com)

License
-------

[](#license)

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

###  Health Score

51

—

FairBetter than 95% of packages

Maintenance100

Actively maintained with recent releases

Popularity21

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity62

Established project with proven stability

 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

Every ~2 days

Total

59

Last Release

1d ago

Major Versions

0.0.41 → 1.0.02026-03-26

### Community

Maintainers

![](https://www.gravatar.com/avatar/1afc3eb4b0906148c94893fc2c76a6832d3c5f6499f6f9ab223ec6d945ee95da?d=identicon)[johntrickett](/maintainers/johntrickett)

---

Top Contributors

[![johntrickett86](https://avatars.githubusercontent.com/u/149476912?v=4)](https://github.com/johntrickett86 "johntrickett86 (15 commits)")

---

Tags

open-sourceapisdksaloonsimpro

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/stitch-digital-simpro-php-sdk/health.svg)

```
[![Health](https://phpackages.com/badges/stitch-digital-simpro-php-sdk/health.svg)](https://phpackages.com/packages/stitch-digital-simpro-php-sdk)
```

###  Alternatives

[saloonphp/laravel-plugin

The official Laravel plugin for Saloon

807.1M201](/packages/saloonphp-laravel-plugin)[ohdearapp/ohdear-php-sdk

An SDK to easily work with the Oh Dear API

743.0M17](/packages/ohdearapp-ohdear-php-sdk)[codebar-ag/laravel-docuware

DocuWare integration with Laravel

1123.7k](/packages/codebar-ag-laravel-docuware)[whatsdiff/whatsdiff

See what's changed in your project's dependencies

771.2k](/packages/whatsdiff-whatsdiff)

PHPackages © 2026

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