Tencent/WeKnora · error

unexpected status %d: %s

Error message

unexpected status %d: %s

What it means

The Notion API returned an HTTP status outside all explicitly handled ranges (not 2xx, 401/403, 404, 429, or 5xx). Unlike retryable cases, this returns immediately with no retry. The message includes the status code and raw response body, which usually contains Notion's JSON error object describing the actual problem.

Source

Thrown at internal/datasource/connector/notion/client.go:144

			lastErr = fmt.Errorf("rate limited: %s", string(respBody))
			if attempt < maxRetries {
				if sErr := sleepWithContext(ctx, wait); sErr != nil {
					return nil, sErr
				}
				continue
			}

		case resp.StatusCode >= 500:
			lastErr = fmt.Errorf("server error %d: %s", resp.StatusCode, string(respBody))
			if attempt < maxRetries {
				if sErr := sleepWithContext(ctx, time.Duration(1<<attempt)*time.Second); sErr != nil {
					return nil, sErr
				}
				continue
			}

		default:
			return nil, fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(respBody))
		}
	}

	if lastErr != nil {
		return nil, fmt.Errorf("%w: %v", datasource.ErrFetchFailed, lastErr)
	}
	return nil, datasource.ErrFetchFailed
}

// Ping verifies the API token is valid by calling GET /v1/users/me.
func (c *notionClient) Ping(ctx context.Context) error {
	_, err := c.doRequest(ctx, http.MethodGet, "/v1/users/me", nil)
	return err
}

// SearchPages returns all pages and databases accessible to the integration.
func (c *notionClient) SearchPages(ctx context.Context) ([]notionPage, error) {
	return c.paginatePages(ctx, http.MethodPost, "/v1/search")

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Read the JSON body in the error message — Notion's code/message fields (e.g. validation_error, conflict_error) identify the real cause.
  2. Validate Notion page/database/block IDs before calling (32-hex or UUID format) — 400 validation_error usually means a bad ID or path.
  3. Check that NotionAPIVersion matches the endpoints being used; data_sources endpoints need API version 2025-09-03 or newer.
  4. For 409 conflict errors, simply retry the request — it's transient.
  5. If a new status keeps appearing, add an explicit case in the switch at client.go:109 to handle it (e.g. map 400 to a typed error).

Example fix

// before
default:
    return nil, fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(respBody))
// after: surface 400/409 explicitly
case resp.StatusCode == http.StatusBadRequest:
    return nil, fmt.Errorf("%w: %s", datasource.ErrBadRequest, string(respBody))
case resp.StatusCode == http.StatusConflict:
    // retryable: fall through to retry logic
    lastErr = fmt.Errorf("conflict: %s", string(respBody))
Defensive patterns

Strategy: validation

Validate before calling

var notionIDRe = regexp.MustCompile(`^[0-9a-fA-F]{32}$|^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$`)
func validNotionID(id string) bool { return notionIDRe.MatchString(strings.ReplaceAll(id, "-", "")) || notionIDRe.MatchString(id) }

Try / catch

if err != nil && strings.Contains(err.Error(), "unexpected status 4") {
    // parse the JSON body from the error for notion's code field
    var apiErr struct{ Code string `json:"code"`; Message string `json:"message"` }
    // log apiErr.Code: validation_error vs conflict_error
}

Prevention

When it happens

Trigger: Any doRequest call receiving a status like 400 (validation_error), 409 (conflict_error / rate_limited variant), 410, or 3xx redirects. E.g. GetPage with a malformed page ID → 400; a 409 conflict from a concurrent edit; a 400 validation error on the search/query POST body.

Common situations: Malformed Notion IDs passed in (missing dashes usually OK, but truncated IDs → 400 validation_error); 409 conflicts when content changes mid-sync; API version mismatch (Notion-Version header) causing rejected requests; an endpoint contract change (e.g. data_sources endpoints require 2025-09-03+).

Related errors


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/13723827ad2313a7. Report an issue: GitHub.