passbolt/passbolt_api · error · BadRequestException

Invalid contain.

Error message

Invalid contain. {inner message}

What it means

Thrown by QueryStringComponent::validateQueryItems when validating the 'contain' query string key. validateContain() rejects association names that are not permitted; the CakeException is re-thrown as a BadRequestException prefixed with 'Invalid contain.' plus the inner reason. It means the client asked to eager-load a relation the endpoint does not expose.

Solutions

  1. Read the inner message after 'Invalid contain.' to see which association was rejected.
  2. Check the endpoint's documentation for its allowed contain parameters.
  3. Correct the association name spelling/casing to one the endpoint supports.
  4. Remove the contain parameter to receive the default response payload.
  5. If the relation should be containable for a plugin use-case, add it to the controller's allowed contain list.

Example fix

// before
GET /resources.json?contain[permission]=1
// after
GET /resources.json?contain[permissions]=1
Defensive patterns

Strategy: validation

Validate before calling

$allowedContains = ['permissions','groups_users','creator','modifier','favorite','secrets','tags']; // per endpoint
foreach (array_keys((array)($query['contain'] ?? [])) as $association) {
    if (!in_array($association, $allowedContains, true)) {
        throw new InvalidArgumentException("Unsupported contain: $association");
    }
}

Type guard

function isAllowedContain(mixed $contain, array $allowed): bool {
    if (!is_array($contain)) return false;
    foreach (array_keys($contain) as $association) {
        if (!is_string($association) || !in_array($association, $allowed, true)) return false;
    }
    return true;
}

Try / catch

try {
    $result = $api->get('/resources.json', ['query' => ['contain' => $contain]]);
} catch (BadRequestException $e) {
    if (str_starts_with($e->getMessage(), 'Invalid contain.')) {
        $result = $api->get('/resources.json'); // retry without contain
    } else {
        throw $e;
    }
}

Prevention

When it happens

Trigger: GET requests with ?contain[...]=1 for an association not in the endpoint's contain whitelist, e.g. ?contain[permissions]=1 on an endpoint that only allows contain[groups_users], or a misspelled association name.

Common situations: Clients assuming every relation is containable; association names changed between plugin/API versions; copying contain parameters from a different endpoint; camelCase/snake_case mismatches in association names.

Understand the failure class

Background: "Invalid query parameter" / "Failed to parse value of ...": fixing bad query string parameters across APIs — this error's family across 36 libraries.

Related errors


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

Appendix: source

Thrown at src/Controller/Component/QueryStringComponent.php:258

                case 'filter':
                    try {
                        self::validateFilters($parameters, $filterValidators);
                    } catch (CakeException $e) {
                        throw new BadRequestException(__('Invalid filter.') . ' ' . $e->getMessage());
                    }
                    break;
                case 'order':
                    try {
                        self::validateOrders($parameters, $allowedQueryItems);
                    } catch (CakeException $e) {
                        throw new BadRequestException(__('Invalid order.') . ' ' . $e->getMessage());
                    }
                    break;
                case 'contain':
                    try {
                        self::validateContain($parameters);
                    } catch (CakeException $e) {
                        throw new BadRequestException(__('Invalid contain.') . ' ' . $e->getMessage());
                    }
                    break;
            }
        }

        return true;
    }

    /**
     * Validate filters
     *
     * @param array|null $filters such as:
     * - search: a string to do a keyword based search
     * - has-access: a resource id
     * - has-users: an array of user uuids
     * - has-manager: an array of user uuids
     * - has-groups: an array of group uuids
     * - has-parent: an array of folder uuids

View on GitHub (pinned to 31c1bbc10f)