OpenNHP/opennhp · error

unexpected status code

Error message

unexpected status code: %d, content: %s

What it means

The upload POST reached the server but returned a non-200 status. UploadFileToNHPServer reads the response body and returns "unexpected status code: %d, content: %s" embedding both the HTTP status code and the server's error payload. The request itself was delivered; the server rejected it at the application/protocol level.

Solutions

  1. Inspect the status code and the embedded content string in the error — the server's response body usually states the exact rejection reason
  2. Verify the server actually implements POST /storage/upload and matches the client's expected ServerResponse format (version match between endpoints/db and the nhp-server build)
  3. If the status is 413, raise the server/proxy body-size limit or split/compress the file before upload
  4. If 401/403, add the required auth credentials/headers to the request (the current code sets only Content-Type at utils.go:252)
  5. If 5xx, check the server logs; the client request format may violate a server-side parser expectation

Example fix

// before
if resp.StatusCode != http.StatusOK {
	bodyBytes, _ := io.ReadAll(resp.Body)
	return "", fmt.Errorf("unexpected status code: %d, content: %s", resp.StatusCode, string(bodyBytes))
}
// after
if resp.StatusCode != http.StatusOK {
	bodyBytes, _ := io.ReadAll(resp.Body)
	return "", fmt.Errorf("upload to %s failed: status %d, body: %s", uploadUrl, resp.StatusCode, string(bodyBytes))
}
// (operator fix for 413: raise nginx client_max_body_size and restart the server)
Defensive patterns

Strategy: type-guard

Validate before calling

// check server exposes the endpoint and accepts your size before uploading
resp, err := http.Get("http://" + host + "/storage/upload")
if err == nil {
	resp.Body.Close()
	if resp.StatusCode == http.StatusNotFound {
		return fmt.Errorf("server %s does not implement /storage/upload", host)
	}
}
if fileInfo.Size() > maxServerUploadSize {
	return fmt.Errorf("file exceeds server upload limit")
}

Type guard

func isRetryableUploadStatus(code int) bool {
	switch code {
	case http.StatusTooManyRequests, http.StatusBadGateway, http.StatusServiceUnavailable, http.StatusGatewayTimeout:
		return true
	default:
		return code >= 500
	}
}

Try / catch

result, err := device.UploadFileToNHPServer(filePath)
if err != nil {
	var status int
	if n, _ := fmt.Sscanf(err.Error(), "unexpected status code: %d", &status); n == 1 {
		switch {
		case status == http.StatusNotFound:
			// wrong server version/endpoint
		case status == http.StatusUnauthorized || status == http.StatusForbidden:
			// add credentials
		case status == http.StatusRequestEntityTooLarge:
			// shrink/split file or raise server limit
		case isRetryableUploadStatus(status):
			// retry after delay
		}
	}
	return err
}

Prevention

When it happens

Trigger: resp.StatusCode != http.StatusOK after client.Do in UploadFileToNHPServer (endpoints/db/utils.go:264-268). Typical causes: 400/404 because the "storage/upload" route doesn't exist on that server build, 401/403 from missing/invalid auth on the upload endpoint, 413 when the file exceeds the server's body-size limit, 5xx from a server-side failure, or the server replying 400 to the initial http:// probe (utils.go:200) which then correctly switches to https.

Common situations: Uploading to a server that doesn't expose /storage/upload (version mismatch); proxy or auth middleware returning 401/403; server max upload size (nginx client_max_body_size or Gin limit) rejecting large files with 413; reverse proxy returning 502/504; hitting the wrong host so an unrelated web app answers.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07). Data as JSON: /api/errors/27cdacf9155d573f. Report an issue: GitHub.

Appendix: source

Thrown at endpoints/db/utils.go:267

		return "", fmt.Errorf("could not create request: %v", err)
	}

	req.Header.Set("Content-Type", writer.FormDataContentType())

	client := &http.Client{
		Timeout: 120 * time.Minute,
	}

	resp, err := client.Do(req)
	if err != nil {
		return "", fmt.Errorf("could not send https request: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		bodyBytes, _ := io.ReadAll(resp.Body)

		return "", fmt.Errorf("unexpected status code: %d, content: %s", resp.StatusCode, string(bodyBytes))
	}

	// read response body
	bodyBytes, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", fmt.Errorf("could not read response body: %v", err)
	}

	// parse response body
	var respBody ServerResponse

	err = json.Unmarshal(bodyBytes, &respBody)
	if err != nil {
		return "", fmt.Errorf("could not parse response body: %v", err)
	}

	duration := time.Since(startTime)
	speed := float64(progress.TotalSize) / duration.Seconds() / (1024 * 1024)

View on GitHub (pinned to 6e04ca5ff0)