Skip to main content

Project Status:

This project is in an early sandbox phase. Right now, the focus is on defining scaffolding files, folder structure, and generation commands. It's not yet available as a Composer package. The examples you see here demonstrate the direction of travel and the developer experience we're aiming for.

Plenipotentiary

Plenipotentiary

Structure, scaffolding, and sanity for API/SDK integrations in Laravel

A Laravel package helping developers structure API integrations so SDKs, REST APIs, SOAP services and MCP proxies (niche) all feel consistent, testable, and swappable. Provides Gateway/Adapter patterns and Artisan scaffolding. You write the integration code; the package adds validation, error handling, idempotency tracking, and a uniform Result interface across all your external services.

The Integration Challenge

Your Laravel app integrates with 3-5+ different types of services: Google Ads (official SDK), Mailchimp (REST), Stripe (official SDK), legacy SOAP, internal APIs. Each implemented differently.

Without a pattern, every integration is a special snowflake: different error handling, different return types, different testing strategies, different logging approaches. Whether you're a solo developer managing 8 integrations or a team of 10, this mix of different integration types creates maintenance chaos.

TL;DR

Think of Pleni like artisan:make for third-party APIs: declare the provider, domain, context, and resource and instantly scaffold the DTOs, gateways, adapters and test harness you need. Plenipotentiary provides the contracts. You still implement the adapter logic (it's not magic), but the code now sits in a consistent, testable, tool-friendly structure.

Flysystem-style consistency for APIs, while recognizing not everything should be abstracted. Flysystem works because filesystems share common verbs. APIs don't: REST is philosophy, SDKs are vendor-specific, SOAP is legacy. Plenipotentiary gives them all the same architectural pattern, so your app doesn't care what's underneath.

plenipotentiary

/ˌplɛnɪpəˈtɛn(t)ʃ(ə)ri/

a person GATEWAY/ADAPTER, invested with the full power of independent action on behalf of their government DOMAIN, typically in a foreign country API_PROVIDER.

Quick Start Examples

Stripe Payment Gateway
eBay Product Search
GitHub API Integration
Internal Admin Tool
MCP Proxy (Niche: Controlled AI Access)
terminal

Goal

A consistent way to work with diverse APIs.

Provide architectural patterns and tooling so that all integrations look consistent from the app's perspective, while allowing full access to underlying APIs.

This means:

  • Consistency across SDK, REST, SOAP, and (optional) MCP proxy integrations
  • Predictability in how your app interacts with ANY external service
  • Testability - same mocking strategy for all integrations
  • Discoverability - new dev sees Gateway pattern everywhere
  • Swappability - change providers without touching business logic
  • Focusability - AI code agents perform best within defined, repeatable patterns. Repetition turns AI output into reliability.

What This Is NOT

Even API creators struggle to maintain good SDKs. Third-party wrappers are doomed from the start. Plenipotentiary gives you patterns, not promises.

  • Not an "API wrapper for X" - Wrappers promise complete coverage but deliver 20-40%. They can't keep up with API evolution.
  • Not a "unified API client" - REST is a philosophy, not a protocol. Universal abstraction is impossible without losing unique provider value.
  • Not complete endpoint coverage - It provides architecture and patterns. You implement the adapters you need.
  • Not hiding complexity - Leaky abstractions break under real use. We provide structure, not magic.
  • Not code generation for its own sake - Generated code is only valuable if it's maintainable and fits your architecture.

The Gateway Pattern: Your Architectural Anchor

Why This Pattern?

The Gateway/Adapter pattern provides a stable interception point between your application and external services. Once this structure is in place, you gain a single, consistent location to layer in production-grade features.

Without it, these concerns scatter across controllers, jobs, and service classes. Impossible to maintain consistently across diverse integrations (SDKs, REST, SOAP).

The pattern isn't magic; it's discipline. It gives you one place to solve each problem, once, for all integrations.

What You Can Layer In

Not every integration needs all of these. A simple lookup needs none. A financial integration needs them all.

1.
Validation - Your app's requirements enforced before the call
2.
Idempotency - Safe retries, no duplicate charges
3.
Test coverage - High confidence in production behavior
4.
Persistence - Audit trail for debugging and compliance
5.
Policy enforcement - Rate limits, budgets, approval workflows
6.
Error handling - Retries, circuit breakers, graceful degradation
7.
Observability - Logging, metrics, distributed tracing

AI agents thrive on patterns. Once you have real-world adapter examples, AI can generate additional adapters that follow your established conventions. With scaffolded tests already in place, it becomes a matter of briefly reviewing AI-generated code rather than writing from scratch; the patterns and test harness provide the guardrails.

Architecture

Promotes understanding vs over-abstraction - Five patterns, one stable platform

Core Principle

Abstract the Abstractable (CRUD): Pleni supports CRUD where it fits but doesn't pretend it covers everything. It offers multiple integration patterns to match different API shapes - each built on Laravel's native tooling and proven libraries like Saloon for HTTP.

How Plenipotentiary Works: The Flow

Your Application

Controllers, Jobs, Commands

You write this

Gateway

Stable, consistent contracts

✓ Plenipotentiary provides

Adapter

API integration logic

You write this

External API

Stripe, Google, etc.

Third-party service

What You Write

Your application code and the Adapter (actual API integration logic). This is NOT a magic wrapper; you still implement the integration, but with structure and safety guardrails.

What Plenipotentiary Provides

The Gateway layer (stable contracts, validation, policies) and scaffolding commands to generate boilerplate. Consistent interfaces enable robust test harnesses that work across all integration; Mock the Gateway, swap adapters for test doubles, verify behavior without external API calls.

Your Application Domain

Use any Laravel pattern you know - all return Result<T>

This is how your code will look. You still need to code the integration (adapter). Plenipotentiary offers a guided adapter approach that gives you structure, stability, and robustness without hiding the API from you.

Controllers

HTTP endpoints

Actions

Lorisleiva Actions

Jobs

Queue workers

Commands

Artisan CLI

Services

Business logic

AI Agents

LLM workflows

Consistent Result Interface Across All Patterns

Every pattern returns a consistent Result<T> interface - whether Result<CanonicalDTO> (CRUD) or Result<{UseCase}DTO> (Operation). Predictable, testable, transport-agnostic. From simplest to most complex syntax:

1
Simplest: Basic Success Check
$result = $gateway->create($dto);

if ($result->isOk()) {
    // Success! Use the canonical DTO
    $campaign = $result->unwrap();
    echo $campaign->externalId; // '12345'
}
What is rawResponse()?

unwrap() returns your domain DTO (CanonicalDTO for CRUD, {UseCase}DTO for Operation - consistent across providers).

rawResponse() returns the actual provider response (Google's MutateCampaignsResponse, Stripe's Charge object, eBay's SearchResponse, etc.).

Use it for: Debugging, logging provider-specific metadata, accessing fields not in your domain DTO.

Consistent Interface Across All Laravel Patterns

Every pattern returns Result<T> with the same methods:isOk(),isErr(),isInvalid(),unwrap(),rawResponse()

Option 1: Direct Gateway Usage
public function store(Request $req, CampaignGateway $gateway) {
    $dto = CampaignCreateDTO::fromArray($req->validated());
    $result = $gateway->create($dto);

    return $result->isOk()
        ? response()->json($result->unwrap())
        : response()->json($result->error(), 400);
}
Option 2: Via Action (recommended)
public function store(Request $req, CreateCampaignAction $action) {
    $result = $action->handle($req->validated());

    return $result->isOk()
        ? response()->json($result->unwrap())
        : response()->json($result->error(), 400);
}

// The Action internally uses the Gateway:
// class CreateCampaignAction {
//     public function __construct(private CampaignGateway $gateway) {}
//     public function handle(array $data): Result {
//         $dto = CampaignCreateDTO::fromArray($data);
//         return $this->gateway->create($dto);
//     }
// }
calls

Five Patterns for Different Integration Types

Different abstraction levels for different integration types. These patterns help you handle heterogeneous integrations (SDKs, REST, SOAP) with a consistent interface.

CRUD Pattern - Abstractable Resource Lifecycle

SDK or REST (Saloon)

Full lifecycle management with Create/Read/Update/Delete operations on resource-based APIs (campaigns, invoices, customers, products).

Use Cases:

Google Ads Campaigns
Stripe Customers
Shopify Products

Returns

Result<CanonicalDTO>

Repository

Optional

Operation Pattern - Use Cases

SDK or REST (Saloon)

For operations beyond CRUD - search, generate, calculate, verify. Use this when the operation isn't about changing resource fields. If you need to pause a campaign (update status field), use CRUD + Laravel Actions instead. Avoids Gateway-calling-Gateway issues.

For operations that don't map to resource field changes

Use Cases:

eBay Search
OpenAI Completions
Calculate Pricing
Verify Availability

Returns

Result<{UseCase}DTO>

Repository

Optional/swappable

REST Pattern - Native Saloon

REST (Saloon)

Clean RESTful APIs using Saloon's native Request/Response pattern. Two modes: (1) Operation-like use cases use {UseCase}DTO with Gateway for validation/policies, (2) Simple calls use pure Saloon without Gateway overhead. For CRUD operations, use the CRUD pattern instead.

For CRUD operations, use CRUD pattern. REST is for operations and simple calls.

Use Cases:

OpenAI Completion
Weather API
GitHub API
SendGrid Email

Returns

Result<{UseCase}DTO> OR Saloon Response

Repository

Flexible

Procedure Pattern - Rapid Prototyping

SDK or REST (Saloon)

Quick prototyping with dynamic operation names for fast iteration and exploration.

Use Cases:

Admin Tools
Quick Scripts
Prototyping
One-off Tasks

Returns

Result<mixed>

Repository

Optional/swappable

MCP Proxy Pattern - Controlled AI Agent Tool Access

HTTP API → MCP (stdio/SSE)

Proxy MCP servers through your Laravel app to add budget tracking, rate limiting, and audit trails when AI agents (Claude, ChatGPT) need controlled access to high-stakes tools (database, email, billing).

Proxies existing MCP servers, doesn't create new ones

Use Cases:

Proxy Database MCP
Proxy Filesystem MCP
Proxy Slack MCP
Proxy Email MCP

Returns

Result<McpToolResult>

Repository

N/A (proxies existing MCP servers)

applies

Gateway Layer: Your Stable Platform

The stable boundary that provides robustness

Gateway = Stable platform. Your application calls the Gateway, never the vendor API directly. When provider APIs change, only the Adapter changes - your Gateway stays stable. All gateways automatically apply cross-cutting concerns through Laravel's native tools.

Logging

Laravel Log facade

Via GatewayPolicy

Retries

Automatic backoff

Via GatewayPolicy

Idempotency

Laravel Cache

Via GatewayPolicy

Error Mapping

Domain exceptions

Via GatewayPolicy

Rate Limiting

Laravel RateLimiter

Via GatewayPolicy

Observability

Metrics & events

Via GatewayPolicy

Collaboration via INPUT_SPEC

All adapters define INPUT_SPEC as their contract. When sharing adapters, INPUT_SPEC becomes an invaluable kickstart - self documenting errors ensures everyone knows exactly what fields are needed, validation rules, and defaults. This is what YOUR domain needs, not everything the API/SDK call supports (See step 4 in the developer workflow).

// CampaignCreate.php
public const INPUT_SPEC = [
'name' => [
'rules' => ['required', 'string', 'min:1', 'max:128'],
],
'status' => [
'rules' => ['nullable', 'in:ENABLED,PAUSED,REMOVED'],
],
'budgetMicros' => [
'rules' => ['nullable', 'numeric', 'min:0'],
],
'budgetResourceName' => [
'rules' => ['nullable', 'string'],
],
// customerId comes from providerContext - auto-injected
'providerContext.google.customerId' => [
'rules' => ['required', 'string'],
'source' => 'env:GOOGLE_ADS_LINKED_CUSTOMER_ID',
],
];
// Gateway validates automatically via INPUT_SPEC
// Teams immediately understand the contract

Understanding the MCP Proxy Pattern: Controlled AI Tool Access

This is a niche pattern for when AI agents (Claude, ChatGPT) need access to high-stakes tools (database queries, email sending, billing operations) and you need budget tracking, rate limiting, and complete audit trails. Your Laravel app acts as a controlled proxy between the AI agent and existing MCP servers.

delegates

Adapter Layer

Your actual API integration code

Gateway delegates to Adapters - Adapters contain your actual API integration code, implemented according to the pattern you selected (CRUD, Operation, REST, Procedure, or MCP Proxy). Each adapter translates between your domain (DTOs, INPUT_SPEC) and the provider's API (SDK calls, HTTP requests, SOAP).

When provider APIs change, you update the Adapter implementation - the Gateway contract and your application code stay stable.

You write this
calls

External API

Third-party services

Adapters call External APIs - The third-party services your application integrates with: Stripe, Google Ads, Mailchimp, OpenAI, eBay, or any custom API. Your application never calls these directly - always through the Gateway → Adapter layer.

This indirection provides stability, testability, and consistent error handling across all your integrations.

Third-party service