Skip to content

400 responses from non-token endpoints are mislabeled as credential errors #78

Description

@asachs01

Summary

HttpClient.handleResponse() throws HaloPsaAuthenticationError("Bad request - invalid credentials or parameters") for any HTTP 400 response that isn't in the recognized {errors: [...]} / {validation_errors: [...]} shape — regardless of whether the 400 has anything to do with credentials. This misleads API consumers (and end users reading the resulting error) into thinking a 400 is an auth/permissions problem when it's actually just a malformed or incomplete request payload.

https://github.com/WYRE-AI/node-halopsa/blob/main/src/http.ts#L164-L174

switch (response.status) {
  case 400:
    // Could be bad credentials on token request or validation error
    if (this.isValidationError(responseBody)) {
      const errors = this.parseValidationErrors(responseBody);
      throw new HaloPsaValidationError('Validation error', errors, responseBody);
    }
    throw new HaloPsaAuthenticationError(
      'Bad request - invalid credentials or parameters',
      400,
      responseBody
    );

Why this is a real problem

The comment (// Could be bad credentials on token request or validation error) shows this was written to cover two genuinely different cases with one branch:

  1. OAuth token endpoint (/token) — HaloPSA follows the OAuth spec here, where a bad client_id/client_secret legitimately comes back as 400 invalid_client/invalid_grant, not 401. HaloPsaAuthenticationError is the right type here.
  2. Any other API endpoint (e.g. POST /Actions, POST /Tickets) — credentials are already validated via the Bearer token (401 is the auth-failure signal for these). A 400 here means the request body itself was rejected — a missing required field, an invalid enum value, etc. This has nothing to do with credentials, but gets the identical HaloPsaAuthenticationError + "invalid credentials or parameters" message.

We hit this in production via halopsa-mcp: a customer's POST /Actions call (adding a ticket note) failed with a plain, non-errors-shaped 400 body, so it surfaced to the caller as "Bad request - invalid credentials or parameters". The customer read that as "my API application's permissions/credentials must be wrong," spent time reviewing their HaloPSA Agent/Application Identity scopes and considering provisioning a new dedicated API user — none of which was the actual problem. The request just needed a field HaloPSA's server-side validation requires that isn't marked required in ActionCreateData.

Suggested fix

Distinguish by request context rather than guessing from the body shape alone:

  • For the token/auth request path, keep throwing HaloPsaAuthenticationError on a 400 (that's accurate there).
  • For all other endpoints, a non-validation-shaped 400 should throw something neutral — e.g. a HaloPsaBadRequestError (or plain HaloPsaError) with a message like "Bad request (400): <endpoint> rejected the request parameters" — not an HaloPsaAuthenticationError, and not the word "credentials" at all.

Happy to send a PR if that approach sounds right — wanted to confirm intended error taxonomy first since HaloPsaAuthenticationError vs a new HaloPsaBadRequestError is a public API surface change for consumers catching specific error classes.

Related

Distinct from #76 (now fixed) — that was about the response shape on success; this is about error classification on a 400 status.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions