BookStackApp/BookStack · error · ApiAuthException

errors.api_user_token_not_found

Error message

errors.api_user_token_not_found

What it means

ApiTokenGuard::validateToken throws ApiAuthException('errors.api_user_token_not_found') when no ApiToken record matches the id parsed from the Authorization header. The guard looks up the token by its public id portion; null means no such token exists in the database. This happens before the secret is ever checked.

Source

Thrown at app/Api/ApiTokenGuard.php:123

        if (empty($authToken)) {
            throw new ApiAuthException(trans('errors.api_no_authorization_found'));
        }

        if (!str_contains($authToken, ':') || !str_starts_with($authToken, 'Token ')) {
            throw new ApiAuthException(trans('errors.api_bad_authorization_format'));
        }
    }

    /**
     * Validate the given secret against the given token and ensure the token
     * currently has access to the instance API.
     *
     * @throws ApiAuthException
     */
    protected function validateToken(?ApiToken $token, string $secret): void
    {
        if ($token === null) {
            throw new ApiAuthException(trans('errors.api_user_token_not_found'));
        }

        if (!Hash::check($secret, $token->secret)) {
            throw new ApiAuthException(trans('errors.api_incorrect_token_secret'));
        }

        $now = Carbon::now();
        if ($token->expires_at <= $now) {
            throw new ApiAuthException(trans('errors.api_user_token_expired'), 403);
        }

        if (!$token->user->can(Permission::AccessApi)) {
            throw new ApiAuthException(trans('errors.api_user_no_api_permission'), 403);
        }
    }

    /**
     * {@inheritdoc}

View on GitHub (pinned to 18f8469a1c)

Solutions

  1. Check the token id in the Authorization header matches an existing token under the user's profile > API Tokens
  2. Recreate the token in the BookStack UI and update the client with the new '<id>:<secret>' pair
  3. Confirm the client targets the correct BookStack instance/environment
  4. If tokens were lost via DB restore, re-issue all tokens for affected integrations

Example fix

// before
'Authorization' => 'Token 999:abc' // id 999 deleted
// after
'Authorization' => 'Token 12:Xy9AbCdeFgHiJkLmNoPqRsTuVwXyZ012' // id re-issued via UI
Defensive patterns

Strategy: try-catch

Validate before calling

// Keep token id/secret together in config and sanity-check both are set and non-empty
if (empty($config['id']) || empty($config['secret'])) {
    throw new RuntimeException('BookStack API token id and secret are both required');
}

Type guard

function hasTokenPair(array $config): bool {
    return isset($config['id'], $config['secret']) && is_string($config['id']) && $config['id'] !== '';
}

Try / catch

try {
    $res = $client->get($url, ['headers' => ['Authorization' => "Token {$id}:{$secret}"]]);
} catch (ClientException $e) {
    if ($e->getResponse()->getStatusCode() === 401) {
        // token not found or bad format: alert and stop, do not blind-retry
    }
    throw $e;
}

Prevention

When it happens

Trigger: Using a token id that was deleted from the user's API tokens; typos in the id portion of 'id:secret'; pointing a client at a different BookStack instance than the one that issued the token; database restored/migrated without the api_tokens rows.

Common situations: Rotating credentials and deleting the old token while a cron job still uses it; copying the secret but mistyping the id; environment mismatch between staging and production tokens.

Related errors


AI-assisted analysis of BookStackApp/BookStack@18f8469a1c (2026-09-02). Data as JSON: /api/errors/471584250126d337. Report an issue: GitHub.