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

  1. Do nothing — the instance is already PRO; verify with the edition/subscription GET endpoint
  2. Use the update/replace subscription endpoint to change an existing PRO key
  3. Delete the current subscription first if a downgrade is intended, then re-create
  4. 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

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


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)