PHPackages                             daikazu/asi-smartlink - 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. daikazu/asi-smartlink

ActiveLibrary[API Development](/categories/api)

daikazu/asi-smartlink
=====================

A typed PHP client for the ASI Smartlink REST API, built on Saloon, with fluent query builders, immutable DTOs, typed exceptions, and optional Laravel integration.

v0.1.0(1mo ago)031↓85.7%MITPHPPHP ^8.3CI passing

Since Jun 5Pushed 1mo agoCompare

[ Source](https://github.com/daikazu/asi-smartlink)[ Packagist](https://packagist.org/packages/daikazu/asi-smartlink)[ Docs](https://github.com/daikazu/asi-smartlink)[ GitHub Sponsors](https://github.com/Daikazu)[ RSS](/packages/daikazu-asi-smartlink/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (1)Dependencies (13)Versions (2)Used By (0)

ASI Smartlink for PHP
=====================

[](#asi-smartlink-for-php)

[![Latest Version on Packagist](https://camo.githubusercontent.com/37ca55b00934d4232c2607351950aa19592c890a1e2f2d4471a1bb11871a81db/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6461696b617a752f6173692d736d6172746c696e6b2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/daikazu/asi-smartlink)[![GitHub Tests Action Status](https://github.com/daikazu/asi-smartlink/actions/workflows/run-tests.yml/badge.svg)](https://github.com/daikazu/asi-smartlink/actions?query=workflow%3Arun-tests+branch%3Amain)[![GitHub Code Style Action Status](https://github.com/daikazu/asi-smartlink/actions/workflows/fix-php-code-style-issues.yml/badge.svg)](https://github.com/daikazu/asi-smartlink/actions?query=workflow%3A%22Fix+PHP+code+style+issues%22+branch%3Amain)[![Total Downloads](https://camo.githubusercontent.com/7d217bfbfe0f2c96c45b6542e1a7f29be290e815888db3b399cbe2dcbdecc2bd/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6461696b617a752f6173692d736d6172746c696e6b2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/daikazu/asi-smartlink)

A typed PHP client for the [ASI Smartlink](https://www.asicentral.com/) REST API, built on [Saloon](https://docs.saloon.dev/). Search products, suppliers, and decorators; pull product detail, charges, and price matrices; resolve media URLs; and drive auto-complete and industry news — all through fluent query builders, immutable DTOs, and typed exceptions. Laravel integration (service provider, facade, publishable config) is included but optional; the client runs equally well in plain PHP.

Warning

**Work in progress — pre-release software.** This package is under active development and has not yet reached a stable `1.0` release. The public API, DTO shapes, and method signatures may change without notice between `0.x` releases. Pin an exact version and review the changelog before upgrading. Not yet recommended for production use.

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

[](#installation)

You can install the package via composer:

```
composer require daikazu/asi-smartlink
```

You can publish the config file with:

```
php artisan vendor:publish --tag="asi-smartlink-config"
```

### Environment

[](#environment)

Add your ASI-issued credentials to `.env`:

```
ASI_SMARTLINK_API_URL=https://api.asicentral.com
ASI_SMARTLINK_CLIENT_ID=your-client-id
ASI_SMARTLINK_SECRET=your-client-secret
# optional
ASI_SMARTLINK_TIMEOUT=30
```

Authentication uses ASI's `AsiMemberAuth` scheme — a single `Authorization`header built from the client ID and secret. There is no OAuth/token exchange.

Usage
-----

[](#usage)

```
use Daikazu\AsiSmartlink\Enums\ProductSort;
use Daikazu\AsiSmartlink\Facades\AsiSmartlink;

$results = AsiSmartlink::products()->search(
    query: 'mugs',
    page: 1,
    resultsPerPage: 25,
    sort: ProductSort::PriceLowToHigh,
    dimensionLists: ['supplier', 'category'],
);

$results->resultsTotal;          // 20552
$results->hasNextPage();         // true

foreach ($results->products as $product) {
    $product->name;              // "Purple Plush Santa Hat"
    $product->number;            // "HAT112"
    $product->supplier?->name;   // "Brighter Promotions Inc"
    $product->prices[0]?->price; // 3.68
}
```

The same is available via the container, e.g. `app(\Daikazu\AsiSmartlink\AsiSmartlink::class)`or by injecting `Daikazu\AsiSmartlink\Http\SmartLinkConnector`.

### Using it without Laravel

[](#using-it-without-laravel)

Laravel is optional. The package only requires `saloonphp/saloon` at runtime — the service provider, facade, and config helpers are conveniences that activate when Laravel is present. Outside Laravel (plain PHP, Symfony, a CLI script, …) just construct the connector directly:

```
use Daikazu\AsiSmartlink\Http\SmartLinkConnector;
use Daikazu\AsiSmartlink\Support\ProductQuery;

$smartlink = new SmartLinkConnector(
    baseUrl: 'https://api.asicentral.com',
    clientId: 123456,
    clientSecret: getenv('ASI_SMARTLINK_SECRET'),
);

$results = $smartlink->products()->search(
    ProductQuery::make('pen')->hasImage()->priceBetween(1, 5)
);
```

The resources (`->products()`, `->suppliers()`, `->decorators()`, `->lists()`, `->media()`), DTOs, query builders, and typed exceptions all work identically — none of them touch the framework. Saloon's own `MockClient` (`$connector->withMockClient(...)`) handles testing without the Laravel `Saloon::fake()` facade.

### Available resources

[](#available-resources)

```
use Daikazu\AsiSmartlink\Facades\AsiSmartlink;
use Daikazu\AsiSmartlink\Enums\AutoCompleteDimension;
use Daikazu\AsiSmartlink\Enums\MediaSize;

// Products
AsiSmartlink::products()->search('mugs');
AsiSmartlink::products()->get(4815380);                 // full detail
AsiSmartlink::products()->getByCpn('12345');            // CPN lookup
AsiSmartlink::products()->configure(4815380, configId: 1);
AsiSmartlink::products()->matrix(4694711);              // apparel price grid
AsiSmartlink::products()->charges(4815380);             // list
AsiSmartlink::products()->lookupCharges();              // master charge list
AsiSmartlink::products()->deletedSince('11/02/2016');   // list

// Suppliers & decorators
AsiSmartlink::suppliers()->search('promotions');
AsiSmartlink::suppliers()->get(875);
AsiSmartlink::decorators()->search('promotions');
AsiSmartlink::decorators()->get(6859235);

// Lists (no auth required)
AsiSmartlink::lists()->autoComplete(AutoCompleteDimension::Category, 'shirts'); // list
AsiSmartlink::lists()->news();                          // list
AsiSmartlink::lists()->newsArticle(46318);

// Media (no auth required)
AsiSmartlink::media()->url(20542352, MediaSize::Normal); // build URL for
AsiSmartlink::media()->get(5072512, MediaSize::Large);   // fetch the image bytes
```

> **Authentication:** most endpoints send the `AsiMemberAuth` credentials automatically. The auto-complete, industry-news, and media endpoints require **no** authentication, so the package deliberately omits the credentials on those requests.

> **Note on detail responses:** `ProductDetail`, `SupplierDetail`, and `DecoratorDetail`type the documented identity/contact fields and expose the complete decoded payload via a `raw` array (and a `get('Key')` helper). The SmartLink PDF's "Raw Response" samples are embedded images, so the deeper nested structures carry `TODO(confirm)` notes pending verification against a live response — see the DTO docblocks.

### Filtering product search

[](#filtering-product-search)

The product search `q` parameter supports dimension and boolean filters. Build them with `ProductQuery` instead of hand-writing the string — `products()->search()` accepts either a plain keyword string or a `ProductQuery`:

```
use Daikazu\AsiSmartlink\Facades\AsiSmartlink;
use Daikazu\AsiSmartlink\Support\ProductQuery;

$query = ProductQuery::make('pen')
    ->preferred(1, 2, 3)        // preferred supplier ranks 1-5 (or ->preferredAll() for *)
    ->category('Pens')
    ->priceBetween(1.00, 3.00)  // price:[1 to 3]
    ->hasImage()                // boolean filters: has_image:1
    ->isNew();

AsiSmartlink::products()->search($query, resultsPerPage: 25);
// q => "pen preferred:1,2,3 category:Pens price:[1 to 3] has_image:1 is_new:1"
```

Covers all tier-1 dimensions (`category`, `color`, `size`, `material`, `supplier`, `asi`, `priceBetween`/`costBetween`/`profitBetween`, `quantity`, `productionTime`, `supplierCountry`, `preferred`), tier-2 dimensions (`shape`, `theme`, `tradeName`, `supplierState`, `imprintMethod`, `lineName`, `supplierRating`), the boolean filters (`hasImage`, `hasPrice`, `hasRushService`, `isNew`, `isSoldBlank`, `isMadeInUsa`, `isConfirmedProduct`, …), and a `raw('filter:value')` escape hatch. Per the ASI docs, combine a tier-2 filter with at least one tier-1 selection, and `preferred:*` slows the response.

Supplier search has its own builder, `SupplierQuery`, accepted by `suppliers()->search()`:

```
use Daikazu\AsiSmartlink\Support\SupplierQuery;

// Filter-only search — suppliers in Philadelphia, PA, rated 4+
AsiSmartlink::suppliers()->search(
    SupplierQuery::make()
        ->state('PA')              // postal abbreviation
        ->city('Philadelphia')
        ->supplierRating(4)        // minimum rating; emits rating:4
);
// q => "state:PA city:Philadelphia rating:4"
```

> **The keyword is AND-ed with every filter.** It matches against the supplier *name*, so pairing a specific keyword with a narrow location easily yields zero results (e.g. `make('promotions')->city('Philadelphia')` finds no supplier *named* "promotions" in Philadelphia). Use the keyword for name searches, or omit it and filter by location. `supplierRating(n)` matches suppliers rated `n` or higher.

Supports `asi`, `zip`, `phone`, `city`, `state`, `email`, `supplierRating`, `canadianFriendly`, `canadian`, and a `raw('filter:value')` escape hatch.

Decorator search has the equivalent `DecoratorQuery` (a smaller filter set: `asi`, `zip`, `phone`, `city`, `state`, `email`), accepted by `decorators()->search()`:

```
use Daikazu\AsiSmartlink\Support\DecoratorQuery;

AsiSmartlink::decorators()->search(
    DecoratorQuery::make('embroidery')->state('FL')->city('Miami')
);
// q => "embroidery state:FL city:Miami"
```

> `state:` expects the **postal abbreviation** (`FL`, `PA`), even though the address in responses contains the full state name.

### Autocomplete → filter

[](#autocomplete--filter)

The dimension filters (`category`, `color`, `supplier`, `theme`, …) match **exact** values, so use the auto-complete endpoint to discover valid values as the user types, then feed the chosen value straight into the query builder. Auto-complete needs no auth and isn't rate-limited, so it's safe to call on each keystroke.

```
use Daikazu\AsiSmartlink\Enums\AutoCompleteDimension;
use Daikazu\AsiSmartlink\Facades\AsiSmartlink;
use Daikazu\AsiSmartlink\Support\ProductQuery;

// 1. user types "bl" in the colour field — suggest matching values
$suggestions = AsiSmartlink::lists()->autoComplete(AutoCompleteDimension::Color, 'bl');
// ["Black Shades", "Blue Shades", "Bright Blue", ...]

// 2. user picks one — feed the exact value into the search filter
$results = AsiSmartlink::products()->search(
    ProductQuery::make('shirt')->color('Blue Shades')
);
```

The `AutoCompleteDimension` cases line up with the builder methods (`Color` → `color()`, `Category` → `category()`, `Supplier` → `supplier()`, `Theme` → `theme()`, and so on). Supplier suggestions come back as `"Hit Promotional Products (asi/61125)"`, so you can show the name and parse the ASI number for an `asi:` filter.

Error handling
--------------

[](#error-handling)

Failed responses (4xx/5xx) throw a typed exception. ASI returns errors as `{"Error": "message"}`; that message is surfaced on the exception. All exceptions extend `SmartLinkRequestException`, so you can catch one or many:

```
use Daikazu\AsiSmartlink\Exceptions\AuthenticationException;
use Daikazu\AsiSmartlink\Exceptions\ResourceNotFoundException;
use Daikazu\AsiSmartlink\Exceptions\RateLimitExceededException;
use Daikazu\AsiSmartlink\Exceptions\SmartLinkRequestException;

try {
    $product = AsiSmartlink::products()->get(4815380);
} catch (ResourceNotFoundException $e) {
    // 404 — {"Error":"Product not found"}
} catch (AuthenticationException $e) {
    // 401/403 — bad or missing client_id / client_secret
} catch (RateLimitExceededException $e) {
    // 429 — hourly limit (5,000) exceeded
    $e->limit();      // 5000
    $e->remaining();  // 0
    $e->reset();      // X-Rate-Limit-Reset
} catch (SmartLinkRequestException $e) {
    // any other API error (e.g. 5xx)
    $e->getStatus();    // HTTP status code
    $e->error();        // ASI "Error" message (null if the body wasn't JSON)
    $e->getResponse();  // the underlying Saloon response
}
```

StatusException401 / 403`AuthenticationException`404`ResourceNotFoundException`429`RateLimitExceededException`other 4xx/5xx`SmartLinkRequestException`Testing
-------

[](#testing)

```
composer test
```

Changelog
---------

[](#changelog)

Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.

Contributing
------------

[](#contributing)

Please see [CONTRIBUTING](CONTRIBUTING.md) for details.

Security Vulnerabilities
------------------------

[](#security-vulnerabilities)

Please review [our security policy](../../security/policy) on how to report security vulnerabilities.

Credits
-------

[](#credits)

- [Mike Wall](https://github.com/daikazu)
- [All Contributors](../../contributors)

License
-------

[](#license)

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

###  Health Score

38

—

LowBetter than 83% of packages

Maintenance90

Actively maintained with recent releases

Popularity9

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity38

Early-stage or recently created project

 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

Unknown

Total

1

Last Release

49d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/4039367?v=4)[Mike Wall](/maintainers/daikazu)[@daikazu](https://github.com/daikazu)

---

Top Contributors

[![daikazu](https://avatars.githubusercontent.com/u/4039367?v=4)](https://github.com/daikazu "daikazu (1 commits)")

---

Tags

laraveldaikazuasi-smartlink

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/daikazu-asi-smartlink/health.svg)

```
[![Health](https://phpackages.com/badges/daikazu-asi-smartlink/health.svg)](https://phpackages.com/packages/daikazu-asi-smartlink)
```

###  Alternatives

[codebar-ag/laravel-docuware

DocuWare integration with Laravel

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

Zammad integration with Laravel

107.1k](/packages/codebar-ag-laravel-zammad)

PHPackages © 2026

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