Control planeAPI reference

Control-plane API reference

All routes are served by api-next. Authentication varies per endpoint and is called out in each section.

Endpoint summary

EndpointMethodAuthPurpose
/license/keysPOSTAdmin bearerMint a license key
/license/activatePOSTKey in bodyExchange a key for an activation token
/license/jwksGETPublicPublish the JWT verification key
/telemetry/downloadPOSTRegistry bearerRecord a package-download event
/boxes/deployPOSTLicense key in bodyRegister box domains
/boxes/deploy/statusGETLicense-key bearerCheck deployment status
/boxes/resolveGETPublicResolve a host to a backend (planned)

Licensing

POST /license/keys

Mint a new license key. Admin-only (bearer secret).

RequestMintLicenseDto:

{
  "customerId": "cus_123",
  "tier": "growth",
  "expiresAt": "2027-01-01T00:00:00.000Z"
}
FieldTypeRequiredNotes
customerIdstringyesOpaque customer identifier
tier'basic' | 'growth' | 'enterprise'yesDetermines entitled packages
expiresAtISO date stringnoOmit for non-expiring keys

Response — the plaintext license key, returned once. Store it securely; it cannot be retrieved again.

POST /license/activate

A box exchanges its license key for an activation token plus its entitled package set. No prior auth — the key is the credential.

RequestActivateDto:

{ "key": "pk_live_..." }

ResponseActivationResult:

{
  "tier": "growth",
  "customerId": "cus_123",
  "token": "<RS256 JWT>",
  "expiresIn": 3600,
  "packages": ["@withpotter/box-core", "@withpotter/product", "..."],
  "registryUrl": "https://registry.withpotter.com"
}
FieldTypeNotes
tokenstring (JWT)RS256, short-lived; used as the registry token
packagesstring[]Dependency-closed entitled set for the tier
registryUrlstringGated registry the CLI installs from
expiresInnumberToken TTL in seconds (default 3600)

See Licensing for the JWT claim shape.

GET /license/jwks

Publish the public key used to verify activation tokens. Consumed by the gated registry.

Response:

{
  "algorithm": "RS256",
  "issuer": "withpotter-control-plane",
  "publicKey": "-----BEGIN PUBLIC KEY-----\n..."
}

POST /telemetry/download

The gated registry reports each package download. Registry-auth only; fire-and-forget.

RequestTelemetryDownloadDto:

{
  "customerId": "cus_123",
  "tier": "growth",
  "packageName": "@withpotter/product",
  "version": "1.7.3",
  "outcome": "allowed"
}
FieldTypeNotes
outcome'allowed' | 'denied'Whether the registry served the package
versionstringOptional

Response: 202 Accepted{ "accepted": true }.


Deployment

POST /boxes/deploy

Register a box’s domains. Authenticated with the license key in the body.

RequestDeployBoxDto:

{
  "key": "pk_live_...",
  "name": "acme-bank",
  "backendUrl": "https://api.acme-bank.com",
  "frontendUrl": "https://acme-bank.com",
  "domains": ["acme-bank.com", "*.shop.acme-bank.com"]
}

ResponseDeployBoxResult:

{
  "registered": ["acme-bank.com", "*.shop.acme-bank.com"],
  "nameservers": ["ns1.vercel-dns.com", "ns2.vercel-dns.com"]
}

The vendor delegates the returned nameservers at their registrar. See Deployment.

GET /boxes/deploy/status

Check a box’s deployment status. License key passed as a bearer token in the Authorization header; the box name is a query parameter.

GET /boxes/deploy/status?name=acme-bank
Authorization: Bearer pk_live_...

Host resolution (planned)

GET /boxes/resolve

⚠️

This endpoint is planned and not yet implemented in api-next. The storefront render app already calls it (with a client-side stub fallback) to map a public host to the bank engine backend that serves it. See Storefront render.

GET /boxes/resolve?host=shop.acme.com

Intended response:

{
  "backendUrl": "https://api.acme-bank.com",
  "match": "exact",
  "tenantSlug": "acme-flagship"
}
FieldTypeNotes
match'exact' | 'wildcard'wildcard carries tenantSlug; exact requires a backend lookup
tenantSlugstringPresent for wildcard matches