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
- Read the inner message after 'Invalid contain.' to see which association was rejected.
- Check the endpoint's documentation for its allowed contain parameters.
- Correct the association name spelling/casing to one the endpoint supports.
- Remove the contain parameter to receive the default response payload.
- 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
- Copy contain parameters only from the specific endpoint's documentation, not other endpoints
- Use the exact association name/casing exposed by the API
- Verify contain lists after every passbolt/plugin upgrade
- Keep a per-endpoint constant of allowed associations in your client
- Drop contain gracefully (default payload) when a relation is not essential
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
- Invalid filter.
- Invalid order.
- " " is not a valid contain value.
- Invalid query string. The contain parameter should be an…
- " " is not a valid datetime for filter .
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 uuidsView on GitHub (pinned to 31c1bbc10f)