Looking to hire Laravel developers? Try LaraJobs

laravel-livespace maintained by beziworld

Description
Laravel client for the Livespace CRM API: persons, companies, deals, tasks, notes, files, users, custom fields and web forms, with multiple accounts
Last update
2026/11/10 21:36 (dev-master)
License
Downloads
0

Comments
comments powered by Disqus

Laravel Livespace

tests Latest Version License

Laravel client for the Livespace CRM API (https://SUBDOMAIN.livespace.io/api/public/json/Module/method):

  • the documented authorization flow: a one-time token from getToken for every call, then _api_sha = sha1(API_KEY . TOKEN . API_SECRET) with the session ID, exactly as the official SDK sends it
  • persons and companies: get, list, attribute search, lazy iteration, add (with duplicate check and wall switches), edit, delete, merge, tags, company assignment, marketing consents, tasks, wall notes and phone calls; company lookup by NIP in any format
  • deals: get, search, lazy iteration, add with price and cost in the account's decimal format, edit, status (open, won, lost, outdated), process stages, tags, tasks, notes
  • tasks (Todo): get, filters by completion, type, period and linked objects, tasks of an object, add, edit, complete, delete
  • wall notes: list with filters, add, edit, delete
  • files: attach a file by URL (with optional HTTP credentials) to persons, companies, deals and spaces
  • users: current user, all users, add, invite, lock, unlock, remove
  • custom fields (datasets) per object type with select options
  • phrase search across contacts, companies and deals
  • web form submissions turned into a person, a company and a follow-up task in one call
  • several Livespace accounts (named connections per subdomain and runtime credentials per tenant)
  • typed, immutable data objects that keep the raw response; money as decimal strings, dates as CarbonImmutable in Europe/Warsaw
  • Livespace result codes (420 validation, 540 no permission, 562 incorrect key, ...) turned into typed exceptions; an unknown subdomain is reported as such; reads retried on 5xx, connection errors and HTTP 429, writes never

All requests go through the Laravel HTTP client, so everything works with Http::fake().

Polska wersja dokumentacji

Why this package

Packagist (November 2026) lists the official livespace/livespace-php SDK (23k downloads, PHP 5.3 era, cURL with certificate verification turned off, no tests) and indahash/livespace (2016), and no Laravel integration. This package is a tested, Laravel-native client built on the official API guide (English version 1.13 of 13 May 2024 and Polish version 1.12) and on the wire format of the official SDK.

Installation

composer require beziworld/laravel-livespace
php artisan vendor:publish --tag=livespace-config   # optional

Generate the keys in Livespace under Settings > API. They belong to one user: every call runs with that user's permissions.

LIVESPACE_SUBDOMAIN=acme          # https://acme.livespace.io (a full URL works too)
LIVESPACE_API_KEY=your-api-key
LIVESPACE_API_SECRET=your-api-secret

Other settings: LIVESPACE_TIMEOUT (30 s), LIVESPACE_CONNECT_TIMEOUT (10 s), LIVESPACE_RETRIES (2, reads only), LIVESPACE_RETRY_DELAY (1000 ms, doubled per attempt), LIVESPACE_MAX_RETRY_WAIT (60 s), LIVESPACE_PAGE_SIZE (500), LIVESPACE_DECIMAL_SEPARATOR (,, as in the API guide examples) and LIVESPACE_TIMEZONE (Europe/Warsaw).

php artisan livespace:check        # default: OK (https://acme.livespace.io, API user Jan Kowalski).
php artisan livespace:check --all

Turn on "Show API identifiers on the object profiles" in Settings > API to see the IDs of contacts, deals, custom fields and sales process stages.

Persons and companies

use Beziworld\Livespace\Facades\Livespace;

$person = Livespace::persons()->create([
    'firstname' => 'Jan',
    'lastname' => 'Kowalski',
    'emails' => ['jan@firma.pl'],
    'phones' => [['phone_no' => '600100200', 'type' => 2, 'is_default' => 1]],   // 1 phone, 2 mobile, 3 fax
    'company' => ['name' => 'Firma Sp. z o.o.', 'nip' => '111-222-55-00'],
    'dataset' => ['87de5630-d889-bdb1-bd09-d61335d27ff8' => 'value'],           // custom fields
], checkIfExists: true, wall: false);

$person = Livespace::persons()->get($person->id);
$person->email();
Livespace::persons()->findByEmail('jan@firma.pl');
Livespace::persons()->search(['tags' => ['vip'], 'modified' => ['from' => now()->subDay()]], limit: 100);

foreach (Livespace::persons()->lazy() as $person) { /* every person, page by page */ }

Livespace::persons()->update($person->id, ['position' => 'CEO']);
Livespace::persons()->tag($person->id, add: ['newsletter'], remove: ['lead']);
Livespace::persons()->assignCompany($person->id, 'Firma Sp. z o.o.');   // ID or name; null removes
Livespace::persons()->setConsents($person->id, [['id' => $consentId, 'status_id' => $statusId]]);
Livespace::persons()->addCall($person->id, '+48600100200', outbound: true, durationSeconds: 231);
Livespace::persons()->merge($person->id, [$duplicateId]);

$company = Livespace::companies()->findByNip('PL 111-222-55-00');
Livespace::companies()->create(['name' => 'Bank XYZ', 'client_status' => 'current_client']);
Livespace::companies()->setClientStatus($company->id, 'former_client');

Deals

$deal = Livespace::deals()->create([
    'name' => 'Website redesign',
    'process_id' => $processId,
    'company' => ['id' => $company->id],
    'contact' => ['id' => $person->id],
    'price' => '12500.00',        // sent as "12500,00" with the default separator
    'date_end' => now()->addMonth(),
]);

Livespace::deals()->search(['status' => ['open'], 'owner_login' => 'jan@firma.pl']);
Livespace::deals()->openForCompany($company->id);
Livespace::deals()->markStages($deal->id, [$stageId, $otherStageId => false]);
Livespace::deals()->markWon($deal->id);
$deal->price;   // "12500.00"

Tasks, notes and files

Livespace::tasks()->create([
    'title' => 'Call back',
    'date' => now()->addDays(2)->setTime(10, 0),
    'priority' => 1,
    'objects' => [['type' => 'deal', 'id' => $deal->id]],
]);
Livespace::tasks()->list(completed: false, from: today(), to: today()->addWeek());
Livespace::tasks()->forObject('company', $company->id);
Livespace::tasks()->complete($taskId);
Livespace::deals()->addTask($deal->id, 'Send the offer', today()->addDay());

Livespace::notes()->add('Offer sent', 'deal', $deal->id, tags: ['offer']);
Livespace::notes()->list('deal', $deal->id, types: ['note', 'email']);

Livespace::files()->attachToDeal($deal->id, URL::temporarySignedRoute('offers.pdf', now()->addHour(), $offer), 'Offer');

Livespace documents only one way to add files: it downloads the file from a URL given with the edit call. A signed temporary route of your application works well. There is no documented upload of file contents or file download.

Users, custom fields and search

Livespace::users()->me();
Livespace::users()->all();
Livespace::users()->invite(['new@firma.pl'], [['structure_id' => [$teamId], 'role_id' => [$roleId], 'is_main' => 1]]);
Livespace::users()->lock('old@firma.pl');

$industry = Livespace::fields()->find('company', 'Industry');
Livespace::companies()->update($company->id, ['dataset' => [$industry->id => $industry->optionId('IT')]]);

Livespace::search()->query('contact', 'jan kowalski');

Web forms

use Beziworld\Livespace\Data\FormSubmission;

// From validated request input (name or first_name/last_name, email, phone, company, nip, message, city, ...)
Livespace::forms()->submit($request->validated(), source: 'contact form');

// Or explicitly
Livespace::forms()->submit(new FormSubmission(
    name: 'Jan Kowalski',
    email: 'jan@firma.pl',
    companyName: 'Firma Sp. z o.o.',
    message: 'Please send an offer',
    tags: ['website'],
    taskTitle: 'Contact the lead',
    taskDate: today()->addDays(3),
));

Livespace has no form endpoint; the API guide (chapter 17) shows exactly this pattern: one Contact/addContact call that creates the person, the company and a follow-up task. Unlike the guide example, duplicate checking stays on so a returning visitor does not create a second contact (checkIfExists: false turns it off).

Several accounts

Livespace::connection('second-company')->deals()->search(['status' => 'open']);

Livespace::using([
    'subdomain' => $tenant->livespace_subdomain,
    'api_key' => $tenant->livespace_key,
    'api_secret' => $tenant->livespace_secret,
])->persons()->create([...]);

Anything else

Every method of the API guide is available through call(), for example spaces, products and cost categories:

Livespace::call('Space/getAll', ['type' => 'space'], read: true);
Livespace::call('Deal/product_add', ['name' => 'Consulting', 'sku' => 'C-1', 'default_price' => 150]);
Livespace::response('Default/ping', ['param1' => 'value1']);   // status, result, data, error

Notes on the API

  • A new token is requested for every call, as the official SDK does; the guide does not say whether a token can be reused.
  • The guide documents request parameters in detail but not the structure of read responses. The data objects map the documented field names and keep the complete record in raw.
  • No rate limits are documented. HTTP 429 is handled with Retry-After.

Errors

Exception When
Exceptions\ApiException "status": false: resultCode() (400 method error, 420 validation, 5xx API errors, 530 not logged in, 540 no permission, 560 to 564 authorization), error(), fieldErrors(), method(), body(); helpers isValidationError(), isAuthenticationError(), isPermissionError(), isUnknownAccount() (HTTP 404 for an unknown subdomain), isRetryable(). Also HTTP errors, unreadable responses and connection failures (isTransportError())
Exceptions\RateLimitExceeded HTTP 429 after the read retries, or on the first write; retryAfter()
Exceptions\InvalidArgument Input violating a documented rule (missing names, unknown statuses or object types, more than 10 merged records, ...)
Exceptions\InvalidConfiguration Missing subdomain or keys, unknown connection

All extend Exceptions\LivespaceException.

Development

composer install
composer ci   # pint --test, phpstan (level 8), pest

Author

Created and maintained by Kamil Ćwiertnia, beziworld.eu.

Need an integration? Get in touch through beziworld.eu.

License

MIT. See LICENSE.md. This package is not affiliated with Livespace sp. z o.o.