{"record":{"id":"3fca9300c5f70063","repo":"passbolt/passbolt_api","slug":"the-instance-is-already-on-pro","errorCode":null,"errorMessage":"The instance is already on PRO.","messagePattern":"The instance is already on PRO\\.","errorType":"http","errorClass":"ConflictException","httpStatus":409,"severity":"error","filePath":"plugins/PassboltCe/Edition/src/Controller/EditionSubscriptionsCreateController.php","lineNumber":71,"sourceCode":"            throw new BadRequestException($e->getMessage());\n        } catch (SubscriptionException $e) {\n            throw new PaymentRequiredException($e->getMessage(), $e->getErrors());\n        }\n\n        $this->success(__('The subscription was created.'), $keyDto->toArray());\n    }\n\n    /**\n     * Rejects with HTTP 409 if the instance is already on PRO or already has a\n     * persisted subscription row.\n     *\n     * @return void\n     * @throws \\Cake\\Http\\Exception\\ConflictException\n     */\n    private function assertNotAlreadyPro(): void\n    {\n        if ((new EditionGetService())->get()->isPro()) {\n            throw new ConflictException(__('The instance is already on PRO.'));\n        }\n\n        $subscriptions = $this->fetchTable('Passbolt/Subscription.Subscriptions');\n        if ($subscriptions->find()->count() > 0) {\n            throw new ConflictException(__('A subscription key is already present.'));\n        }\n    }\n}\n","sourceCodeStart":53,"sourceCodeEnd":80,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/plugins/PassboltCe/Edition/src/Controller/EditionSubscriptionsCreateController.php#L53-L80","documentation":"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.","triggerScenarios":"POSTing a new subscription key when the current edition metadata already identifies the instance as PRO.","commonSituations":"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.","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"],"exampleFix":"// before\nawait api.post('/edition/subscriptions', {data: key}); // 409 if already PRO\n// after\nconst edition = await api.get('/edition');\nif (!edition.isPro) {\n  await api.post('/edition/subscriptions', {data: key});\n}","handlingStrategy":"validation","validationCode":"const edition = await api.get('/edition');\nif (edition.body.isPro) { throw new Error('Instance already PRO; skip create.'); }","typeGuard":"function canCreateSubscription(edition: {isPro: boolean}): boolean { return !edition.isPro; }","tryCatchPattern":"try {\n  await api.post('/edition/subscriptions', {data: key});\n} catch (e) {\n  if (e.status === 409) { /* already PRO: use update flow or no-op */ }\n}","preventionTips":["Query edition state before any subscription mutation","Make upgrade automation idempotent (check-then-act)","Use the update endpoint to replace keys on PRO instances"],"tags":["conflict","subscription","http-409"],"backgroundTag":"invalid-state-transition","analyzedSha":"31c1bbc10f32808a607fa9bd81891e898779c0bc","analyzedAt":"2026-09-17T00:04:38.960Z","contentChangedAt":"2026-09-17T00:04:38.960Z","schemaVersion":2},"datasetVersion":"2026-09-21T09:17:21.228Z"}