Why does api-license alone hold the Statamic credential, instead of every service that might eventually need a license doing its own lookup? Because the alternative is credential sprawl, and credential sprawl is a cost that compounds instead of a cost you pay once.
The rule is simple: each outside vendor, Statamic, GitHub, the registrar and CDN, the AI provider, is reached by exactly one internal service, and that service is the only holder of that vendor's credentials. Everyone else who needs the capability goes through the owner. You've already met the enum that names this arrangement, back in Chapter 13:
// app/Enums/Dependency.php
enum Dependency: string
{
case GitHub = 'github'; // repos + commits (via api-git)
case Forge = 'forge'; // server provisioning + deploy
case Stripe = 'stripe'; // billing + subscriptions
case OpenAI = 'openai'; // AI content + image generation
case Registrar = 'registrar'; // domain registration + DNS
}
Every case is a vendor, and the comment next to it names the one service that's allowed to know that vendor exists. api-server doesn't hold a Statamic key of its own. It holds a client that talks to api-license, and lets api-license be the only place in the entire fleet that knows what a Statamic license payload actually looks like:
// app/Http/Integrations/Cloud/LicenseAPI.php
final class LicenseAPI
{
use SendsRequests;
public function __construct(
protected PendingRequest $request,
) {}
public static function boot(string $apiKey): LicenseAPI
{
return new LicenseAPI(
request: Http::acceptJson()->withToken(
token: $apiKey,
)->baseUrl(
url: Config::string(/* api-license's base URL */),
)->timeout(
seconds: 60,
),
);
}
public function getLicenses(): array
{
// GETs api-license's own listing route
return (array) $this->send()->json();
}
public function addLicense(string $name, string $url): array
{
// POSTs api-license's own create route
return (array) $this->send(payload: [
'name' => trim($name),
'domain' => trim($url),
])->json();
}
}
LicenseAPI is api-server's view of the license capability, and notice what it isn't: it isn't a second Statamic driver. It's a plain HTTP client pointed at api-license's own routes, the same routes api-license exposes to any authorized caller. api-server never imports a Statamic SDK, never stores a Statamic token, never has an opinion about how Statamic structures its errors. If Statamic changes its API tomorrow, exactly one codebase changes: api-license's driver. Every other service that needed a license kept calling LicenseAPI::getLicenses() and never noticed.
This is the same Manager, Facade, Driver idea Chapter 1 introduced for LicenseManager, applied one level up. Inside api-license, the Manager hides which vendor is underneath from the rest of that app's code. Across the fleet, the owning-service rule hides which vendor is underneath from every other app. It's one idea, applied twice, at two different radii. That's not a coincidence, it's what happens when a pattern is actually good: it keeps being the right answer as the scope around it grows.