codebes/gripp-sdk

PHP SDK for the Gripp CRM/ERP API. Fluent query builder, batch operations, and 54 resources for project management, invoicing, time tracking, and more.

Maintainers

Package info

github.com/Codebes-NL/gripp-sdk

pkg:composer/codebes/gripp-sdk

Statistics

Installs: 5

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.2.1 2026-03-02 14:21 UTC

This package is auto-updated.

Last update: 2026-03-02 14:22:44 UTC


README

Latest Version on Packagist Tests PHPStan License: MIT PHP Version

A PHP SDK for the Gripp (now Exact Gripp) CRM/ERP API. Manage companies, contacts, projects, invoices, time tracking, and 50+ other resources through a fluent query builder with batch operations, auto-pagination, and automatic retries. Works with any PHP 8.1+ application, including Laravel.

Features

  • Fluent query builder with 14 filter operators
  • Full CRUD support on 54 Gripp resources
  • Batch operations (multiple API calls in a single HTTP request)
  • Auto-pagination for large datasets
  • Automatic retries on server errors and connection failures
  • Typed exceptions for authentication, rate limiting, and API errors
  • Laravel Collection responses out of the box
  • Self-documenting resources with field types, required fields, and relationship metadata

Requirements

  • PHP 8.1+
  • A Gripp API token and API URL

Installation

composer require codebes/gripp-sdk

Configuration

Option 1: Environment variables

Set GRIPP_API_TOKEN in your .env file or environment:

GRIPP_API_TOKEN=your-api-token

Then call configure without arguments:

use CodeBes\GrippSdk\GrippClient;

GrippClient::configure();

The SDK uses https://api.gripp.com by default. To override, set GRIPP_API_URL in your environment.

Option 2: Explicit configuration

use CodeBes\GrippSdk\GrippClient;

GrippClient::configure(token: 'your-api-token');

Option 3: Interactive setup

vendor/bin/gripp-setup

This creates a .env file with your credentials.

Quick Start

use CodeBes\GrippSdk\GrippClient;
use CodeBes\GrippSdk\Resources\Company;
use CodeBes\GrippSdk\Resources\Contact;
use CodeBes\GrippSdk\Resources\Project;

// Configure once at application boot
GrippClient::configure();

// Find a record by ID
$company = Company::find(123);

// Get all records (auto-paginated)
$allCompanies = Company::all();

// Query with filters
$activeCompanies = Company::where('active', true)
    ->orderBy('companyname', 'asc')
    ->limit(50)
    ->get();

// Create a record
$result = Company::create([
    'companyname' => 'Acme Corp',
    'relationtype' => 'COMPANY',
    'email' => 'info@acme.com',
]);

// Update a record
Company::update(123, [
    'phone' => '+31 20 123 4567',
]);

// Delete a record
Company::delete(123);

Query Builder

The query builder provides a fluent interface for filtering, ordering, and paginating results.

use CodeBes\GrippSdk\Resources\Project;

// Simple equality filter (two-argument form)
$projects = Project::where('company', 42)->get();

// With operator (three-argument form)
$projects = Project::where('name', 'contains', 'Website')->get();

// Chain multiple filters
$results = Project::where('company', 42)
    ->where('archived', false)
    ->orderBy('createdon', 'desc')
    ->limit(25)
    ->offset(0)
    ->get();

// Get just the first match
$project = Project::where('name', 'contains', 'Redesign')->first();

// Count matching records
$count = Project::where('archived', false)->count();

Supported Filter Operators

Operator Description
equals Exact match (default when using two-argument where)
notequals Not equal to
contains String contains
notcontains String does not contain
startswith String starts with
endswith String ends with
greaterthan Greater than
lessthan Less than
greaterequals Greater than or equal to
lessequals Less than or equal to
in Value is in array
notin Value is not in array
isnull Field is null (pass true as value)
isnotnull Field is not null (pass true as value)

Date Helpers

Common date filtering patterns are built into the query builder:

use CodeBes\GrippSdk\Resources\Project;
use CodeBes\GrippSdk\Resources\Hour;

// Filter by year
$projects = Project::where('archived', false)
    ->whereYear('createdon', 2026)
    ->get();

// Filter by month
$hours = Hour::where('employee', 42)
    ->whereMonth('date', 2026, 3)
    ->get();

// Filter by date range
$invoices = Invoice::where('company', 10)
    ->whereDateBetween('date', '2026-01-01', '2026-03-31')
    ->get();

// Modified since (for incremental syncing)
$updated = Project::where('archived', false)
    ->whereModifiedSince(new DateTime('2026-03-01 00:00:00'))
    ->get();

Auto-Prefixing

Field names are automatically prefixed with the entity name when using the query builder. Writing Project::where('createdon', ...) produces the filter field project.createdon. Fully qualified fields like project.createdon are left as-is.

Auto-Pagination

Both get() and the query builder's get() automatically paginate through all results. Use limit() when you only want a specific number of results (single API call):

// Fetches ALL matching projects across all pages
$all = Project::where('archived', false)->get();

// Fetches only the first 25 (single page)
$page = Project::where('archived', false)->limit(25)->get();

Batch Operations

Group multiple API calls into a single HTTP request for better performance:

use CodeBes\GrippSdk\GrippClient;
use CodeBes\GrippSdk\Resources\Company;
use CodeBes\GrippSdk\Resources\Contact;

$transport = GrippClient::getTransport();
$transport->startBatch();

// Queue multiple calls (these don't execute yet)
Company::find(1);
Company::find(2);
Contact::find(10);

// Execute all queued calls in a single HTTP request
$responses = $transport->executeBatch();

foreach ($responses as $response) {
    $rows = $response->rows();
    // Process each response...
}

Rate Limit Awareness

The transport tracks rate limit headers from API responses and provides hooks for proactive budget management:

$transport = GrippClient::getTransport();

// Check current rate limit state (from most recent response headers)
$transport->getRateLimitRemaining(); // e.g. 847
$transport->getRateLimitLimit();     // e.g. 1000

// Abort requests when budget is low
$transport->beforeRequest(function (int $requestCount, ?int $remaining, ?int $limit) {
    if ($remaining !== null && $remaining <= 5) {
        throw new \RuntimeException("Only {$remaining} API calls left!");
    }
});

// React when a 429 or 503 rate limit hits
$transport->onRateLimitExceeded(function (?int $retryAfter, ?int $remaining) {
    // Set a flag, notify monitoring, etc.
    Log::warning("Gripp rate limit hit, retry after {$retryAfter}s");
});

The SDK treats both HTTP 429 and HTTP 503 with Gripp error code 1004 (short-burst throttle) as rate limit errors.

Error Handling

The SDK throws specific exceptions for different error types:

use CodeBes\GrippSdk\Exceptions\AuthenticationException;
use CodeBes\GrippSdk\Exceptions\RateLimitException;
use CodeBes\GrippSdk\Exceptions\RequestException;
use CodeBes\GrippSdk\Exceptions\GrippException;

try {
    $company = Company::find(123);
} catch (AuthenticationException $e) {
    // 401 or 403 - invalid token or forbidden
    if ($e->isTokenInvalid()) {
        // Handle invalid/expired token
    }
    if ($e->isForbidden()) {
        // Handle insufficient permissions
    }
} catch (RateLimitException $e) {
    // 429 - too many requests
    $retryAfter = $e->getRetryAfter(); // seconds to wait
    $remaining = $e->getRemaining();   // remaining requests
} catch (RequestException $e) {
    // Other API errors
    $data = $e->getResponseData(); // raw error response
} catch (GrippException $e) {
    // Base exception for all SDK errors (e.g. not configured)
}

Available Resources

All resources support read operations. Resources that also support create, update, and/or delete are indicated below.

Resource Create Read Update Delete
AbsenceRequest x x x x
AbsenceRequestLine x x x x
BulkPrice x x x x
CalendarItem x x x x
Company x x x x
CompanyDossier x x x x
Contact x x x x
Contract x x x x
ContractLine x x x x
Cost x
CostHeading x x x x
Department x x x x
Employee x x x x
EmployeeFamily x x x x
EmployeeTarget x
EmployeeYearlyLeaveBudget x x x
EmploymentContract x x x x
ExternalLink x x x x
File x
Hour x x x x
Invoice x x x x
InvoiceLine x x x x
Ledger x x x x
Memorial x
MemorialLine x
Notification
Offer x x x x
OfferPhase x x x x
OfferProjectLine x x x x
Packet x x x x
PacketLine x x x x
Payment x x x x
PriceException x x x x
Product x x x x
Project x x x x
ProjectPhase x x x x
PurchaseInvoice x x x x
PurchaseInvoiceLine x x x x
PurchaseOrder x x x x
PurchaseOrderLine x x x x
PurchasePayment x x x x
RejectionReason x x x x
RevenueTarget x
Tag x x x x
Task x x x x
TaskPhase x x x x
TaskType x x x x
TimelineEntry x x x x
UmbrellaProject x x x
Unit x x x x
Webhook x x x x
YearTarget x
YearTargetType x

Special resources:

  • Notification has custom emit() and emitall() methods instead of CRUD.
  • Company has additional getCompanyByCOC(), addInteractionByCompanyId(), and addInteractionByCompanyCOC() methods.

Resource Metadata

Every resource class exposes constants that describe its schema:

use CodeBes\GrippSdk\Resources\Company;

Company::FIELDS;    // ['id' => 'int', 'companyname' => 'string', ...]
Company::READONLY;  // ['createdon', 'updatedon', 'id', 'searchname', 'files']
Company::REQUIRED;  // ['relationtype']
Company::RELATIONS; // ['accountmanager' => Employee::class, 'tags' => Tag::class, ...]
  • FIELDS maps field names to their types (string, int, float, boolean, datetime, date, array, customfields, color)
  • READONLY lists fields that cannot be written to
  • REQUIRED lists fields that must be provided when creating/updating
  • RELATIONS maps foreign key fields to their related resource classes

Auto-Pagination

Both all() and get() automatically handle pagination, fetching all matching records transparently:

// Fetches all companies, regardless of how many pages it takes
$companies = Company::all(); // Returns Illuminate\Support\Collection

// Filtered queries also auto-paginate
$active = Company::where('active', true)->get(); // All pages

Response Format

All collection methods return Illuminate\Support\Collection instances. Single-record methods return associative arrays or null.

$companies = Company::where('active', true)->get();

// Use Collection methods
$names = $companies->pluck('companyname');
$grouped = $companies->groupBy('visitingaddress_city');
$first = $companies->first();

Testing

composer test

Or directly:

vendor/bin/phpunit

Changelog

Please see the GitHub Releases page for more information on what has changed recently.

Contributing

Contributions are welcome! Please open a pull request against the main branch. All PRs require:

  • Passing tests (composer test)
  • Code style compliance (composer cs)
  • Static analysis passing (composer analyse)
  • Code owner approval

License

MIT - see LICENSE for details.