appwrite/appwrite · error · Appwrite\Extend\Exception

user_id_missing

user_id_missing

Error message

When using account API key, make sure to pass x-appwrite-user header with your user ID.

What it means

Thrown in the account-API-key validator (app/init/resources/request.php:1048) when a decoded API key carries a userId but the x-appwrite-user header is missing or does not match. This binds the account key to the specific user it was issued for.

Source

Thrown at app/init/resources/request.php:1048

        if (empty($key)) {
            return null;
        }

        $key = Key::decode($project, $team, $user, $key);

        $userHeader = $request->getHeaderLine('x-appwrite-user');
        $organizationHeader = $request->getHeaderLine('x-appwrite-organization');
        $projectHeader = $request->getHeaderLine('x-appwrite-project');

        if (! empty($key->getProjectId())) {
            if (empty($projectHeader) || $projectHeader !== $key->getProjectId()) {
                throw new Exception(Exception::PROJECT_ID_MISSING);
            }
        }

        if (! empty($key->getUserId())) {
            if (empty($userHeader) || $userHeader !== $key->getUserId()) {
                throw new Exception(Exception::USER_ID_MISSING);
            }
        }

        if (! empty($key->getTeamId())) {
            if (empty($organizationHeader) || $organizationHeader !== $key->getTeamId()) {
                throw new Exception(Exception::ORGANIZATION_ID_MISSING);
            }
        }

        return $key;
    }, ['request', 'project', 'team', 'user']);

    $context->set('resourceToken', function ($project, $dbForProject, $request, Authorization $authorization) {
        $tokenJWT = $request->getParam('token');

        if (! empty($tokenJWT) && ! $project->isEmpty()) { // JWT authentication
            // Use a large but reasonable maxAge to avoid auto-exp when token has no expiry
            $jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), RESOURCE_TOKEN_ALGORITHM, RESOURCE_TOKEN_MAX_AGE, RESOURCE_TOKEN_LEEWAY); // Instantiate with key, algo, maxAge and leeway.

View on GitHub (pinned to cd368e707d)

Solutions

  1. Set x-appwrite-user (client.setHeader or SDK equivalent) to the key's userId.
  2. Confirm the userId in the key matches the user the client means to act as.
  3. Ensure proxies do not strip x-appwrite-user.
  4. If using the SDK, configure it to send both the key and the user header together.

Example fix

// before
client.setHeader('x-appwrite-key', accountApiKey);
// after
client.setHeader('x-appwrite-key', accountApiKey);
client.setHeader('x-appwrite-user', userId);
Defensive patterns

Strategy: validation

Validate before calling

// Ensure x-appwrite-user is set whenever an account API key is used
function ensureUserHeader(headers, keyUserId) {
  if (keyUserId && headers['x-appwrite-user'] !== keyUserId) {
    headers['x-appwrite-user'] = keyUserId;
  }
  return headers;
}

Prevention

When it happens

Trigger: Client authenticates with an account API key whose payload includes a userId, but x-appwrite-user is absent or mismatched. The check at line ~1046 compares $userHeader !== $key->getUserId().

Common situations: SDK not configured to send x-appwrite-user when using an account API key; header stripped by proxy; key for user A used with a client pointing at user B; SSR setup missing the user header.

Related errors


AI-assisted analysis of appwrite/appwrite@cd368e707d (2026-08-12). Data as JSON: /api/errors/ca5e9dd3c4df69cf. Report an issue: GitHub.