origami-mp/origami-api-sdk
origami-mp/origami-api-sdk is a PHP SDK for the Origami API, providing a convenient client to streamline authentication and endpoint requests. It helps Laravel and PHP developers integrate with Origami services faster with less boilerplate and more consistent API handling.
1. Install and confirm what you actually got.
composer require origami-mp/origami-api-sdk
Before writing a line of app code, read three things in the vendor tree:
composer.json — the require block (is illuminate/support there, or is it framework-agnostic PHP?), the autoload PSR-4 root, and any extra.laravel.providers / extra.laravel.aliases. If there is no service provider, there is no config file to publish and you wire everything yourself.src/ — the layout is the documentation here. Expect Data, Guest, Oauth API repositories, a ParamBags (or similar) tree, DTOs, client repositories, Exceptions, Enums, Helpers.tests/ — the single best source of usage examples. In this package the conventions doc explicitly tells contributors to "refer to real code… by class name", which means the tests are the reference implementation for every repository method, param bag and DTO.2. Configure credentials once.
Given the SDK splits Guest (public) from Data (authenticated) and Oauth, expect at minimum a base URL and an API key/token. If a config file exists:
php artisan vendor:publish --tag=origami-api-config
ORIGAMI_API_URL=https://api.example.test
ORIGAMI_API_KEY=...
If it does not, register a singleton in a AppServiceProvider so you inject a configured client repository rather than newing it up in controllers:
$this->app->singleton(OrigamiClientRepository::class, fn () => new OrigamiClientRepository(
baseUrl: config('services.origami.url'),
apiKey: config('services.origami.key'),
));
3. First use case: one read, end to end.
Start with a Guest repository method — it removes auth from the equation and lets you confirm transport, DTO hydration and exception mapping in one call:
// illustrative shape; check the real signatures in src/ and tests/
$params = new ListingIndexParamBag();
$params->setInclude(['seller', 'photos']);
$params->setPage(1);
try {
$listing = $guestRepository->getListing($id, $params); // returns a DTO
return response()->json($listing->toArray());
} catch (OrigamiApiSdkException $e) {
report($e);
abort(502, 'Upstream unavailable');
}
Then repeat with a Data repository to confirm the key is being sent, and an Oauth one only when you actually have a user token flow.
Inject repositories, never construct them in controllers. Each API repository method is "HTTP call in, DTO out", which makes it a clean dependency. Constructor-inject into a thin application service, and let the controller stay dumb:
final class ListingService
{
public function __construct(private ListingApiRepository $listings) {}
public function detail(int $id): ListingData
{
return $this->listings->get($id, (new ListingParamBag())->setInclude(['seller']));
}
}
Treat DTOs as your read model, not as entities. They hydrate from the API response and validate it, so they are safe to hand to a Blade view, an API Resource, or Inertia props via toArray(). Do not leak them into Eloquent relationships or ->save() anything from them — they are response-shaped, not persistence-shaped.
Param bags are input, DTOs are output, and they are not symmetric. Build a param bag per request, compose nested bags for sub-resources, and keep the bag immutable-ish per call site (don't share one instance across calls and mutate it — mutation bugs here are silent, since the bag carries no validation).
Writes go through the same seam but behind a queue. Because every mutation is one repository method, wrapping it in a job is trivial and gives you retries plus a natural place to log the OrigamiApiSdkException family instead of surfacing it to the user.
Map exception families to HTTP status in one place. The SDK groups API exceptions into families, and an API error picks a specific one. Register a handler/renderable that translates SDK exceptions into 4xx/5xx once, rather than try/catching in every controller. Anything unmapped → 502 with the upstream error logged, never echoed.
Test at the seam, not through it. Either fake the HTTP layer (Http::fake() if the client repository is built on the Laravel HTTP client) or bind a stub client repository in the container. The client repositories exist precisely so HTTP can be swapped — that is the extension point the SDK authors designed for. Write one contract test per repository method against recorded/known fixtures and let the rest of your suite hit the stub.
Ask for includes explicitly. Related data is not returned unless requested through the include rule. In a list endpoint, requesting includes you don't render is the most common cause of slow pages; requesting none and then calling $dto->seller->name is the most common cause of nulls.
src/ and tests/ before estimating any work.composer.json ("origami-mp/origami-api-sdk": "1.*", not ^), and wrap it in your own App\Services\Origami\* layer so an upstream breaking change is a one-file fix.init<Property>() call on the DTO before they hydrate. If a collection comes back empty when the API clearly returned items, this is your bug, not theirs.toArray() is overridden last in every DTO and includes commented-out entries for fields that aren't always present. Don't "clean up" those comments — they document the include contract. If a key is missing from your JSON, check whether the include was requested first."sellers/{$id}"), array spread instead of array_merge()/+, and a fixed element order (traits, cases, constants, properties, constructor, setUp(), then public/protected/private, toArray() last). Run vendor/bin/pint with the package's pint.json if you patch the SDK in a fork, or your diff will be all whitespace.Rule::enum() in your Form Request — one source of truth for allowed values. Adding a case to a shared enum touches every place it's used; grep before you add.<Name>Contract; new transport behaviour = override a hook on a client repository; new helper = static methods, no business logic, with tests. A change introducing a genuinely new pattern is expected to update the skill file in the same PR — so if the conventions don't cover your case, follow the nearest existing pattern and say so in the PR rather than inventing one.origami_api repo when a response shape surprises you. The SDK mirrors the API's actual format, and the API repo is the authority on what a field really contains.How can I help you explore Laravel packages today?