cardtechie/tradingcardapi-sdk-php
A PHP SDK for the Trading Card API that makes it easy to search cards, fetch card details, and integrate trading card data into Laravel or any PHP app with a clean, simple client. Ideal for building card databases, marketplaces, and collection tools.
composer require cardtechie/tradingcardapi-sdk-php:^0.3.4
Requires PHP ^8.1 and works on Laravel 9–12. There is no mention of a service provider in the release notes, so before assuming Laravel-native wiring, open composer.json and confirm whether extra.laravel.providers is declared. If it isn't, you're integrating a plain PHP SDK — register your own singleton binding in AppServiceProvider and skip the facade fantasy.
Where to look first:
README.md → the authentication section. As of 0.3.4 there are now three modes: legacy PAT, OAuth, and the new api_key mode. Pick deliberately.TradingCardApi entry class — it exposes withApiKey() alongside whatever OAuth constructor/factory already existed.config/tradingcardapi.php (if publishable) or the documented env vars. The new key is TRADINGCARDAPI_API_KEY.Internal\ namespace — treat it as off-limits.First use case: a read-only catalog surface. Configure TRADINGCARDAPI_API_KEY with a portal-issued tc_ key and pull card/set metadata into a local cache table. This is the safest possible first integration because tc_ keys are read-only — you physically cannot corrupt remote state while you're still learning the response shapes.
// .env
TRADINGCARDAPI_API_KEY=tc_xxxxxxxxxxxx
// AppServiceProvider::register()
$this->app->singleton('tradingcardapi.read', fn () => TradingCardApi::withApiKey(
config('services.tradingcardapi.api_key')
));
Note the use of config() with a services.php entry rather than env() directly — see gotchas.
Two-client pattern. This is the single most important structural decision in 0.3.4. Because tc_ keys are read-only, you need two distinctly named bindings:
tradingcardapi.read — api_key mode, used by controllers, view composers, search endpoints.tradingcardapi.write — OAuth mode, used only by sync/ingest jobs and admin actions.Name them differently in the container so a future developer cannot accidentally inject the read client into a write path. Give the write client its own config block with separate credentials.
Cache aggressively. Card metadata is slow-moving and API calls are the latency floor of your request. Wrap read calls in Cache::remember keyed on the query signature, with a TTL measured in hours, not minutes. Store the raw response payload alongside your normalised columns so you can rehydrate without a second API call when you add a field.
Sync via queued jobs, not request time. Ingest sets and cards through ShouldBeQueue jobs, dispatched in a Bus::batch() so you can track progress and retry failures per-item. Attach RateLimited middleware and define a limiter in AppServiceProvider — the SDK almost certainly does not self-throttle.
Wrap the SDK behind your own repository interface. Do not let TradingCardApi calls spread across controllers. Define CardRepository / SetRepository contracts with Eloquent-backed or cache-backed implementations. This buys you three things: testability without a live API, the ability to swap read client for write client inside a single method, and insulation from the 0.x version churn.
Testing. Http::fake() will not intercept the SDK if it ships its own Guzzle client — check for an injectable HTTP handler or base-URI override before writing feature tests. If neither exists, the repository interface above is your only seam. In tests, bind a fake repository and never instantiate the SDK at all.
Config precedence. Read the README for which auth mode wins when multiple credential sets are present in the environment. Don't leave TRADINGCARDAPI_API_KEY set in a staging environment that also has OAuth credentials and hope the SDK picks the one you meant.
Read-only is enforced server-side, not client-side. tc_ keys will fail at the HTTP layer on writes — but the failure arrives as an API error mid-job, not as a type error. If a write path silently gets the read client injected, you'll discover it in production logs rather than at boot. Consider a distinct type or a final readonly wrapper class per client so static analysis catches the swap.
Internal\ calls are off the table with api_key mode. The release notes call this out explicitly. Audit any existing code for Internal\ usage before migrating an integration to the new mode — it will break, and it will break at runtime.
Internal\ has no backwards-compatibility promise. Even on OAuth, treat that namespace as private. Pin your usage to the public surface; the 0.3.x line is still moving.
Use config(), not env(), at runtime. This is vanilla Laravel advice but it bites hard here: if you call env('TRADINGCARDAPI_API_KEY') inside a service provider closure that runs after php artisan config:cache, you get null and a confusing 401. Put the key in config/services.php and read it through the config repository.
Pin the minor version. ^0.3.4 is right; ^0.3 invites a surprise. This release changed the authentication surface — a 0.4 could change it again without a major-version signal, because there isn't one yet.
Set explicit HTTP timeouts. SDK defaults are frequently "wait forever." Configure connect and total timeouts, and retry only on 429 and 5xx with exponential backoff — never on 4xx, which includes the read-only rejection you'll be debugging.
Log request identifiers. Most hosted APIs return a request ID header. Capture it in your exception handler so support tickets to the vendor are actionable.
Keep the portal key server-side only. The tc_ key is read-only but still identifies your account and quota. Never surface it to a frontend bundle or a mobile client; proxy through your own endpoints.
Rotation. Portal-issued keys rotate through the vendor's UI. Design the config read so that a key change requires only an env update and a config cache clear — no code deploy.
How can I help you explore Laravel packages today?