passbolt/passbolt_api · error · ConflictException
The instance is already on PRO.
Error message
The instance is already on PRO.
What it means
Before creating a subscription, create() calls assertNotAlreadyPro(), which throws ConflictException if EditionGetService reports the instance is already on PRO. Importing another key onto a PRO instance is an invalid state transition, so the request is refused with 409.
Solutions
- Do nothing — the instance is already PRO; verify with the edition/subscription GET endpoint
- Use the update/replace subscription endpoint to change an existing PRO key
- Delete the current subscription first if a downgrade is intended, then re-create
- Skip the import step in automation when the instance is already PRO
Example fix
// before
await api.post('/edition/subscriptions', {data: key}); // 409 if already PRO
// after
const edition = await api.get('/edition');
if (!edition.isPro) {
await api.post('/edition/subscriptions', {data: key});
} Defensive patterns
Strategy: validation
Validate before calling
const edition = await api.get('/edition');
if (edition.body.isPro) { throw new Error('Instance already PRO; skip create.'); } Type guard
function canCreateSubscription(edition: {isPro: boolean}): boolean { return !edition.isPro; } Try / catch
try {
await api.post('/edition/subscriptions', {data: key});
} catch (e) {
if (e.status === 409) { /* already PRO: use update flow or no-op */ }
} Prevention
- Query edition state before any subscription mutation
- Make upgrade automation idempotent (check-then-act)
- Use the update endpoint to replace keys on PRO instances
When it happens
Trigger: POSTing a new subscription key when the current edition metadata already identifies the instance as PRO.
Common situations: Re-running an upgrade/import that already succeeded; automation retrying a completed upgrade; attempting to replace an existing PRO key through the create endpoint instead of update/delete flows.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- A subscription key is already present.
- $e->getMessage() (from SubscriptionException)
- $e->getMessage() (from SubscriptionSignatureException)
- Only administrators can update the subscription details.
- Only administrators can view the subscription details.
AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17).
Data as JSON: /api/errors/3fca9300c5f70063.
Report an issue: GitHub.
Appendix: source
Thrown at plugins/PassboltCe/Edition/src/Controller/EditionSubscriptionsCreateController.php:71
throw new BadRequestException($e->getMessage());
} catch (SubscriptionException $e) {
throw new PaymentRequiredException($e->getMessage(), $e->getErrors());
}
$this->success(__('The subscription was created.'), $keyDto->toArray());
}
/**
* Rejects with HTTP 409 if the instance is already on PRO or already has a
* persisted subscription row.
*
* @return void
* @throws \Cake\Http\Exception\ConflictException
*/
private function assertNotAlreadyPro(): void
{
if ((new EditionGetService())->get()->isPro()) {
throw new ConflictException(__('The instance is already on PRO.'));
}
$subscriptions = $this->fetchTable('Passbolt/Subscription.Subscriptions');
if ($subscriptions->find()->count() > 0) {
throw new ConflictException(__('A subscription key is already present.'));
}
}
}
View on GitHub (pinned to 31c1bbc10f)