Search by

oleksyuk / laravel-gleif-sdk

maks-oleksyuk

Laravel SDK for the GLEIF LEI API, built on Saloon

Package info

github.com/maks-oleksyuk/laravel-gleif-sdk

pkg:composer/oleksyuk/laravel-gleif-sdk

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

dev-main 2026-10-26 14:17 UTC

This package is auto-updated.

Last update: 2026-10-26 14:33:36 UTC


README

laravel-gleif-sdk

CI Coverage PHP Version Laravel Version Latest Version Total Downloads

Laravel SDK for the GLEIF LEI API, built on Saloon.

Installation

composer require oleksyuk/laravel-gleif-sdk

The service provider and the Gleif facade are auto-discovered. The GLEIF API is public, so there are no credentials to configure.

Usage

use Oleksyuk\Gleif\Facades\Gleif;

$record = Gleif::leiRecord()->find('984500A8D612DCC67997')->dto();

echo $record->entityLegalName['name']; // Global Legal Entity Identifier Foundation

Every method returns a Saloon Response: call ->dto() for typed objects from Oleksyuk\Gleif\Data, or ->json() for the raw payload. On an error response ->dto() throws Saloon's RequestException instead of mapping it: e.g. NotFoundException when a related record doesn't exist (an entity without a direct parent) or BadRequestException for a pageSize above 200. An empty, . or .. ID throws InvalidArgumentException before anything is sent. The facade proxies to Oleksyuk\Gleif\GleifConnector, which can be injected instead. It is scoped, so a mock client or header added to it doesn't leak into the next Octane request or queued job.

Covered APIs

Method GLEIF API
leiRecord() LEI records, ISINs, level 2 parents/children, related records, modifications
leiIssuer() LEI issuers (LOUs) and their jurisdictions
vLeiIssuer() vLEI issuers
fuzzyCompletion() Fuzzy completions
autoCompletion() Auto completions
field() Fields
country() Countries
region() Regions
jurisdiction() Jurisdictions
entityLegalForm() Entity legal forms (ELF)
officialOrganizationalRole() Official organizational roles (OOR)
registrationAuthority() Registration authorities
registrationAgent() Registration agents
export() Export of 1–300 LEI records in an ExportFormat (CSV, XLSX, JSON, XML)

Searching

use Oleksyuk\Gleif\Enums\FuzzyCompletionField;
use Oleksyuk\Gleif\Enums\RegistrationStatus;

$records = Gleif::leiRecord()->all(
    entityLegalName: 'Global Legal Entity',
    registrationStatus: RegistrationStatus::Issued,
    pageSize: 50,
)->dto();

$suggestions = Gleif::fuzzyCompletion()->search('gleif', FuzzyCompletionField::EntityLegalName)->dto();

Pagination

all() returns one page (pageSize is at most 200, and GLEIF caps page numbers at 10,000 results). To walk every page lazily, pass a request to paginate(): it follows the links.next GLEIF returns and uses cursor pagination for LEI records, so it isn't capped:

use Oleksyuk\Gleif\Requests\LeiRecords\Lvl1Information\AllLeiRecords;

$paginator = Gleif::paginate(new AllLeiRecords(entityLegalAddressCountry: 'UA'))->setPerPageLimit(200);

foreach ($paginator->items() as $item) {
    // $item is an Oleksyuk\Gleif\Data\LeiRecord, as from ->dto()
}

Caching

GLEIF publishes a new Golden Copy three times a day (the 00:00, 08:00 and 16:00 UTC copies come out at 02:00, 10:00 and 18:00 UTC), so responses are cached in your Laravel cache until the next publication. The API picks up a new copy hours later, though: a LEI record answer from an older copy than the latest published one (its meta.goldenCopy.publishDate) is cached for 15 minutes only, so the new data shows up soon after the API has it:

gantt
    title One day of GLEIF data in UTC, with the API 3 hours behind the files
    dateFormat HH:mm
    axisFormat %H:%M

    section Golden Copy
    00 UTC copy → files out        : 00:00, 2h
    08 UTC copy → files out        : 08:00, 2h
    16 UTC copy → files out        : 16:00, 2h

    section GLEIF API
    serves the previous 16 UTC copy : done, 00:00, 5h
    serves the 00 UTC copy          : done, 05:00, 8h
    serves the 08 UTC copy          : done, 13:00, 8h
    serves the 16 UTC copy          : done, 21:00, 3h

    section Cache of one LEI
    cached until the 02 UTC files  : active, 00:00, 2h
    API behind → asks every 15 min : crit, 02:00, 3h
    cached until the 10 UTC files  : active, 05:00, 5h
    API behind → asks every 15 min : crit, 10:00, 3h
    cached until the 18 UTC files  : active, 13:00, 5h
    API behind → asks every 15 min : crit, 18:00, 3h
    cached until the 02 UTC files  : active, 21:00, 3h
Loading

GLEIF doesn't say when the API picks up a copy; in practice it takes hours after the files. Caching also covers 404 answers, such as an unknown LEI or an entity without a parent: they don't change in between either. To skip a request that can only answer 404, check the record first: $record->hasDirectParent and $record->hasUltimateParent tell whether GLEIF links a parent record or reports an exception instead. Pick a dedicated store via .env:

GLEIF_CACHE_STORE=redis

It falls back to cache.default. To publish the config file:

php artisan vendor:publish --tag=gleif-config

To skip the cache for one request, send the request yourself:

use Oleksyuk\Gleif\Requests\LeiRecords\Lvl1Information\LeiRecordById;

Gleif::send(new LeiRecordById('984500A8D612DCC67997')->disableCaching());

Rate limit

GLEIF allows 60 requests per minute. The connector tracks that limit in the same cache store and sleeps until a slot frees up instead of failing; a 429 from GLEIF is handled the same way, using Retry-After. Cached responses don't count against the limit. Keep that in mind in web requests: a burst of uncached calls can block for up to a minute.

Testing your code

Use Saloon's mocking: no request leaves your app.

use Oleksyuk\Gleif\Requests\LeiRecords\Lvl1Information\LeiRecordById;
use Saloon\Http\Faking\MockClient;
use Saloon\Http\Faking\MockResponse;

MockClient::global([
    LeiRecordById::class => MockResponse::make(['data' => [/* ... */]]),
]);

Development

task lint            # PHPStan (max level), Pint, Rector, composer validate
task test            # Pest
task test:coverage   # Pest, fails below 100% code coverage; HTML report in build/coverage (needs Xdebug or PCOV)
task test:types      # Pest, fails below 100% type coverage
task fix             # auto-fix Rector / Pint issues
task contract:update # refresh the GLEIF endpoint snapshot used by ContractTest
task fixtures:update # refresh the real GLEIF responses in tests/Fixtures

GLEIF publishes no OpenAPI spec, so tests/Contract/gleif-api.json is a snapshot of the endpoints in its Postman collection: ContractTest checks that every request hits a documented endpoint and every documented endpoint has a request. Fixtures in tests/Fixtures are real GLEIF API resource objects, so the DTO tests check the mapping against what the API actually returns. Both refresh commands need network access; review their diff before committing.