Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Relesys Users Laravel Package

getsno/relesys-users

Laravel client for the Relesys User Management API that gives you simple access to users, departments, user groups, custom fields and communication endpoints, with sorting, filtering and pagination support.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install the right package name. The README's install command is wrong: the repository and Packagist package are getsno/relesys-users, not getsno/relesys-api.

composer require getsno/relesys-users

2. Pick the major that matches your framework floor. The README badge block (PHP ^8.1 / Laravel 10) is stale relative to the newest release. Majors track Laravel majors:

Package major Targets
^1.x Laravel 10, PHP 8.1
^2.x Laravel 11
^4.0 Laravel 13, PHP ^8.3 (testbench ^11, PHPUnit ^12)

Because releases have shipped real breaking changes outside strict semver expectations (readonly properties, enum case renames, a removed value object), pin an exact version rather than floating a caret constraint:

composer require getsno/relesys-users:4.0.0

3. Credentials and scope. Add to .env, then confirm the Relesys side grants the relesys.api.users scope — the code will authenticate fine and then 403 on every user call without it:

RELESYS_CLIENT_ID=""
RELESYS_CLIENT_SECRET=""

4. Where to look first. Three places, in this order:

  • src/Api/UserManagement/ — the entities, patches, and enums. This is the real contract; the README is a sketch.
  • The registered service provider and facade accessor — check whether a config file is publishable (php artisan vendor:publish --tag=relesys-config or similar) and where credentials are actually read from.
  • tests/ — the most reliable documentation in the repo. It's testbench-based, so it shows correct constructor shapes, correct method signatures, and correct enum case names in a runnable form.

5. First use case: read before you write. The cheapest end-to-end validation of credentials, scope, and response shapes is a single paginated read from php artisan tinker:

use Getsno\Relesys\Api\ApiQueryParams;
use Getsno\Relesys\Api\UserManagement\Enums\UserStatus;

$query = (new ApiQueryParams)
    ->addFilter('status', UserStatus::Activated->value)
    ->sortBy('name')
    ->limit(10);

$page = Relesys::users()->getUsers(queryParams: $query, page: 1);
dump($page);

Inspect the returned batch object's properties (items/data, total count, page metadata) before writing any sync loop — the pagination envelope shape determines your loop termination condition, and it isn't documented in the README.

Only after a read succeeds should you attempt createUser. Note that this package is a transport + DTO layer, not a user store: there is no Eloquent model, no local persistence, no sync engine, no events. Every system-of-record, idempotency, and reconciliation decision is yours.

Implementation Patterns

Wrap it. Don't sprinkle Relesys:: calls through controllers. Even a thin boundary buys you testability and an exit path if the single maintainer disappears:

namespace App\Services\Relesys;

class UserProvisioner
{
    public function __construct(private DirectoryResolver $directories) {}

    public function provision(NewHire $hire): string
    {
        $user = User::fromArray([
            'name'                => $hire->fullName,
            'primaryDepartmentId' => $this->directories->departmentId($hire->department),
            'phoneNumber'         => [
                'countryCode' => $hire->phoneCountryCode,
                'number'      => $hire->phoneNumber,
            ],
            'userGroups'          => $this->directories->userGroups($hire->groups),
        ]);

        return Relesys::users()->createUser($user)->id;
    }
}

Store the returned Relesys UUID on your local model (users.relesys_id) as the join key. Without it, every reconciliation becomes an email/name fuzzy match.

Resolve referential IDs before creating anything. primaryDepartmentId and every userGroups[].id are Relesys-side UUIDs, and they differ between sandbox and production. Never hardcode them in code or config; resolve them at runtime and cache:

$departments = Cache::remember('relesys.departments', 3600, fn () =>
    collect(Relesys::departments()->getDepartments()->items)
        ->pluck('id', 'name')
);

User groups additionally require a dataSource discriminator — the README examples use 'RelesysAPI'; keep that literal unless the vendor docs say otherwise.

Prefer patches over read-modify-write. UserPatch is fluent and partial. This is the correct way to update one field:

$patch = (new UserPatch())
    ->title('Team Lead')
    ->birthDate(Carbon::parse('1991-02-05'))
    ->secondaryPhoneNumber(PhoneNumber::fromArray([
        'countryCode' => 47, 'number' => '777777',
    ]));

Relesys::users()->updateUser($uuid, $patch);

Do not getUser() → mutate the entity → send the whole entity back. You will clobber fields your integration doesn't own (custom fields, employment dates, anything a Relesys admin changed in the UI) and you'll race with the UI.

Model lifecycle flows as explicit state transitions. Offboarding is a status change, not a delete:

Relesys::users()->changeUserStatus($uuid, UserStatus::Disabled);

Verify the actual arity of changeUserStatus in src/Api/UserManagement/Users.php — the README calls it with only the status, which cannot be right. Enum cases are camelCase (UserStatus::Activated, UserStatus::Disabled) since v1.0.4/v1.0.5.

Paginate defensively. Pagination is page-number based with no cursor or snapshot guarantee. Under concurrent writes from the Relesys UI, rows shift between pages: you will see duplicates and you will skip records. Treat it as at-least-once:

$seen = [];
for ($page = 1; $page <= $maxPages; $page++) {
    $batch = Relesys::users()->getUsers(queryParams: $query, page: $page);
    if (empty($batch->items)) break;
    foreach ($batch->items as $user) {
        if (isset($seen[$user->id])) continue;
        $seen[$user->id] = true;
        // upsert
    }
}

Always cap $maxPages. A loop with no ceiling plus a filter the API silently ignores equals an infinite loop against a production vendor.

Put bulk work on the queue with a rate ceiling. There are no documented rate limits, so assume there are some. Use Bus::batch() or chunked jobs, and throttle with RateLimiter::for('relesys', fn () => Limit::perMinute(60)). Retry only on 429, 5xx, and connection errors — never on 4xx validation, which will just fail identically forever and burn your queue.

Make creates idempotent. If the same new hire event arrives twice, the second createUser will fail. Catch the conflict-class error, look the user up by employee id / email, and return the existing UUID. Design the job so re-delivery is a no-op.

Test against fakes, not the live tenant. Your own test suite should fake HTTP at the Laravel client boundary (Http::fake(['api.relesysapp.net/*' => Http::response($fixture, 200)])) using fixtures captured from the tests/ directory of the package — those fixtures are real recorded payloads and are the best spec you have. Never commit live credentials; keep the package's own live mode (copy phpunit.xml.dist phpunit.xml, uncomment the env vars) strictly local, exactly as its README describes.

Use the communication endpoints for the human side of provisioning. Since v1.0.6 the communication resource covers messages and templates — sending a welcome message on create is a five-line addition and saves a separate integration.

Gotchas and Tips

The README's first two instructions are both wrong. composer require getsno/relesys-api targets a package name that doesn't match the repo, and the PHP 8.1 / Laravel 10 claim contradicts the current release's PHP 8.3 / Laravel 13 floor. Budget half a day at onboarding to establish ground truth from composer.json and releases/, and treat the README as illustrative rather than authoritative.

The README code samples contain at least two errors. The create example does $newUser = Relesys::users()->createUser($newUser); — it passes an undefined variable where $user was just built. The status example omits the user identifier entirely. Both are copy-paste traps: read the actual method signatures in src/ before trusting a snippet.

customFields cannot be an empty array on input. This is a known vendor quirk papered over in v1.0.7. If you build a user array programmatically, omit the key entirely rather than setting 'customFields' => []. This is exactly the class of bug that produces a cryptic 400 that looks like your payload is fine.

The CustomFields value object was removed in v1.0.6 (the vendor's structure is undocumented). Don't reach for it; Relesys::customFields()->getCustomFields() returns a batch response of raw-ish data — treat its shape as unstable.

Enum case naming changed in v1.0.4 and again in v1.0.5. Older tutorials, Stack Overflow answers, and your own autocomplete from an earlier major will suggest SCREAMING_SNAKE. It's camelCase now. Any ->value usage in query filters is fine; the case name is not.

Error handling lives in RelesysHttpClientException. Since v1.0.5 it carries a failedRequest property — dump the response body from it during integration; it's the fastest way to see what Relesys actually objected to. When you log it, scrub the Authorization header and any PII from the body; user records are exactly the payload this exception will contain.

Token lifecycle is undocumented. There's no documented token caching, refresh, or rotation strategy in the package. Wrap calls so a 401 triggers one re-auth and one retry, and add a single retry on transient failures — but don't build an elaborate token cache until you've confirmed the package doesn't already do it, or you'll double-fetch tokens and trip the vendor's auth rate limit.

Null-vs-omit is a semantic decision, not a syntactic one. Nullable fields have accepted null since v1.0.1, so ->title(null) will go through. Whether that clears the field or ignores it is a vendor contract question. Test it once in sandbox against a disposable user before shipping any "clear field" feature.

A 2026-10-22 release date needs verification before you pin. Cross-check the tag against the Packagist version and the changelog contents. A tag whose commit contents don't match its release notes is a supply-chain smell, and with 0 stars / 0 dependents / one maintainer, this package is effectively a private vendor SDK — fork or vendor the source under your org and pin exactly.

Pagination has no stability guarantee. Any bulk sync must tolerate duplicates and drift by design. Dedupe on the Relesys UUID, key your local upsert on relesys_id, and log counts (seen / created / updated / skipped) so drift is visible rather than silent.

There is no event or webhook surface. "Real-time user changes" is not something you can get from this package. Either poll on a schedule with a status/updated filter, or build webhook reception yourself if Relesys offers it — the package has no opinion.

Debug with the vendor's own contract, not the SDK's. When something behaves oddly, compare your call to the Relesys v1.1 User Management docs and to the recorded fixtures in /tests. The package is a 1:1 mapping by design, so a mismatch between your call and the docs almost always localizes the bug.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator