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
- Check the token id in the Authorization header matches an existing token under the user's profile > API Tokens
- Recreate the token in the BookStack UI and update the client with the new '<id>:<secret>' pair
- Confirm the client targets the correct BookStack instance/environment
- 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
- Re-issue tokens whenever the target environment changes
- Never delete tokens without first deprecating the integrations using them
- Store id and secret as one versioned credential pair in your secrets manager
- Log the token id (never the secret) in client diagnostics
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
- errors.api_bad_authorization_format
- Unauthorized
- errors.email_confirmation_awaiting
- errors.api_no_authorization_found
- errors.api_incorrect_token_secret
AI-assisted analysis of BookStackApp/BookStack@18f8469a1c (2026-09-02).
Data as JSON: /api/errors/471584250126d337.
Report an issue: GitHub.