{"record":{"id":"86f0d76cef8163dc","repo":"passbolt/passbolt_api","slug":"invalid-contain-inner-message","errorCode":null,"errorMessage":"Invalid contain. {inner message}","messagePattern":"Invalid contain\\. (.+?)","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"src/Controller/Component/QueryStringComponent.php","lineNumber":258,"sourceCode":"                case 'filter':\n                    try {\n                        self::validateFilters($parameters, $filterValidators);\n                    } catch (CakeException $e) {\n                        throw new BadRequestException(__('Invalid filter.') . ' ' . $e->getMessage());\n                    }\n                    break;\n                case 'order':\n                    try {\n                        self::validateOrders($parameters, $allowedQueryItems);\n                    } catch (CakeException $e) {\n                        throw new BadRequestException(__('Invalid order.') . ' ' . $e->getMessage());\n                    }\n                    break;\n                case 'contain':\n                    try {\n                        self::validateContain($parameters);\n                    } catch (CakeException $e) {\n                        throw new BadRequestException(__('Invalid contain.') . ' ' . $e->getMessage());\n                    }\n                    break;\n            }\n        }\n\n        return true;\n    }\n\n    /**\n     * Validate filters\n     *\n     * @param array|null $filters such as:\n     * - search: a string to do a keyword based search\n     * - has-access: a resource id\n     * - has-users: an array of user uuids\n     * - has-manager: an array of user uuids\n     * - has-groups: an array of group uuids\n     * - has-parent: an array of folder uuids","sourceCodeStart":240,"sourceCodeEnd":276,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/src/Controller/Component/QueryStringComponent.php#L240-L276","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nGET /resources.json?contain[permission]=1\n// after\nGET /resources.json?contain[permissions]=1","handlingStrategy":"validation","validationCode":"$allowedContains = ['permissions','groups_users','creator','modifier','favorite','secrets','tags']; // per endpoint\nforeach (array_keys((array)($query['contain'] ?? [])) as $association) {\n    if (!in_array($association, $allowedContains, true)) {\n        throw new InvalidArgumentException(\"Unsupported contain: $association\");\n    }\n}","typeGuard":"function isAllowedContain(mixed $contain, array $allowed): bool {\n    if (!is_array($contain)) return false;\n    foreach (array_keys($contain) as $association) {\n        if (!is_string($association) || !in_array($association, $allowed, true)) return false;\n    }\n    return true;\n}","tryCatchPattern":"try {\n    $result = $api->get('/resources.json', ['query' => ['contain' => $contain]]);\n} catch (BadRequestException $e) {\n    if (str_starts_with($e->getMessage(), 'Invalid contain.')) {\n        $result = $api->get('/resources.json'); // retry without contain\n    } else {\n        throw $e;\n    }\n}","preventionTips":["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"],"tags":["http","bad-request","query-string","validation","contain"],"backgroundTag":"invalid-query-parameter","analyzedSha":"31c1bbc10f32808a607fa9bd81891e898779c0bc","analyzedAt":"2026-09-17T00:04:38.960Z","contentChangedAt":"2026-09-17T00:04:38.960Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}