passbolt/passbolt_api · error · PaymentRequiredException

$e->getMessage() (from SubscriptionException)

Error message

$e->getMessage() (from SubscriptionException)

What it means

Other SubscriptionException failures from EditionUpgradeService::upgrade() (non-signature issues) are rethrown as PaymentRequiredException with the original message and errors. This typically means the key is structurally readable but rejected for subscription reasons such as expiry or licensing terms.

Solutions

  1. Check the key's expiry date and obtain a renewed subscription key from passbolt
  2. Read the error details returned in the PaymentRequired error body for the specific rejection reason
  3. Synchronize the server clock (NTP) if expiry-related
  4. Contact passbolt sales/support if the key should be valid

Example fix

// before
// old expired key.txt used for import
// after
// download renewed subscription key and import it
Defensive patterns

Strategy: try-catch

Validate before calling

// check key expiry metadata before import if available
const expired = keyMeta && new Date(keyMeta.expires) < new Date();

Try / catch

try {
  await api.post('/edition/subscriptions', {data: key});
} catch (e) {
  if (e.status === 402) { /* PaymentRequired: renew key; inspect e.errors for reason */ }
}

Prevention

When it happens

Trigger: upgrade() throws SubscriptionException other than a signature failure — e.g. expired subscription key, key data that cannot be parsed into a valid subscription record, or license terms violation.

Common situations: Importing an expired PRO key; renewing with a stale key; a key generated for a different passbolt edition; server clock skew making a valid key appear expired.

Related errors


AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17). Data as JSON: /api/errors/4e1aefe7959cf045. Report an issue: GitHub.

Appendix: source

Thrown at plugins/PassboltCe/Edition/src/Controller/EditionSubscriptionsCreateController.php:55

     * @return void
     */
    public function create(): void
    {
        $this->User->assertIsAdmin();

        $keyString = $this->getRequest()->getData('data');
        if (!is_string($keyString) || trim($keyString) === '') {
            throw new BadRequestException(__('Subscription key data is required.'));
        }

        $this->assertNotAlreadyPro();

        try {
            $keyDto = (new EditionUpgradeService())->upgrade($keyString, $this->User->getAccessControl());
        } catch (SubscriptionSignatureException $e) {
            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.'));
        }

View on GitHub (pinned to 31c1bbc10f)