gitroomhq/postiz-app · error · HttpException
{ error: 'invalid_client' }
Error message
{ error: 'invalid_client' } What it means
Returned as HTTP 401 with OAuth error code invalid_client when exchanging an authorization code for tokens and the client_id does not match any registered OAuth application. This follows RFC 6749 section 5.2 client authentication failure semantics.
Source
Thrown at libraries/nestjs-libraries/src/database/prisma/oauth/oauth.service.ts:257
codeExpiresAt,
codeChallenge: pkce?.codeChallenge,
codeChallengeMethod: pkce?.codeChallengeMethod,
redirectUri: pkce?.redirectUri,
});
return code;
}
async exchangeCodeForToken(
code: string,
clientId: string,
clientSecret?: string,
codeVerifier?: string,
redirectUri?: string
) {
const app = await this._oauthRepository.getAppByClientId(clientId);
if (!app) {
throw new HttpException(
{ error: 'invalid_client' },
HttpStatus.UNAUTHORIZED
);
}
// Public clients (dynamic registration with token_endpoint_auth_method=none)
// authenticate with PKCE instead of a client secret
const isPublicClient = app.dynamic && app.tokenEndpointAuthMethod === 'none';
if (!isPublicClient) {
if (
!clientSecret ||
!app.clientSecret ||
app.clientSecret !== AuthService.fixedEncryption(clientSecret)
) {
throw new HttpException(
{ error: 'invalid_client' },
HttpStatus.UNAUTHORIZED
);View on GitHub (pinned to 0f1647f749)
Solutions
- Verify the client_id matches the OAuth app's clientId exactly (no extra spaces/quotes)
- Confirm the app exists in the same environment/database you're hitting
- If dynamically registered, re-register to obtain a fresh client_id and secret
Example fix
// before
const res = await fetch(tokenEndpoint, { body: new URLSearchParams({ grant_type: 'authorization_code', client_id: 'postiz-prod-abc', code }) });
// after
const res = await fetch(tokenEndpoint, { body: new URLSearchParams({ grant_type: 'authorization_code', client_id: process.env.POSTIZ_CLIENT_ID!.trim(), code }) }); Defensive patterns
Strategy: validation
Validate before calling
if (!await clientExists(clientId)) { throw new Error('Unknown client_id — check the OAuth app settings'); } Type guard
const hasClientId = (id?: string): id is string => !!id && id.trim().length > 0;
Try / catch
try { await exchange(code); } catch (e) { if (e?.response?.data?.error === 'invalid_client') { throw new Error('client_id not recognized on this server'); } throw e; } Prevention
- Load client_id/secret from typed env config, trimmed
- Keep environment-specific client ids separated
When it happens
Trigger: POST to the token endpoint with a grant_type=authorization_code request whose client_id is unknown, typo'd, deleted, or from a different environment.
Common situations: Copy-paste of the wrong client ID; app deleted or not yet propagated; using production client_id against staging server or vice versa; trailing whitespace in env var.
Related errors
- { error: 'invalid_grant' }
- { error: 'invalid_grant', error_description: 'Code has expir
- { error: 'invalid_grant', error_description: 'code_verifier
- invalid_grant
- Token request failed: ${error}
AI-assisted analysis of gitroomhq/postiz-app@0f1647f749 (2026-08-27).
Data as JSON: /api/errors/e631744f7fa2a8fb.
Report an issue: GitHub.