gastownhall/beads · error
Notion API error %s (%d): %s
Error message
Notion API error %s (%d): %s
What it means
Notion returned a non-2xx status and its JSON error body carried a structured message, so the library surfaces it as 'Notion API error <code> (<status>): <message>'. This is the server rejecting the request: bad token, missing permissions, invalid payload, rate limiting, etc. apiErr.Code is Notion's machine-readable error code (e.g. unauthorized, object_not_found, rate_limited).
Source
Thrown at internal/notion/client.go:299
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
defer func() { _ = resp.Body.Close() }()
body, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseBytes))
if err != nil {
return nil, fmt.Errorf("read response: %w", err)
}
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
return body, nil
}
var apiErr struct {
Code string `json:"code"`
Message string `json:"message"`
}
if err := json.Unmarshal(body, &apiErr); err == nil && apiErr.Message != "" {
return nil, fmt.Errorf("Notion API error %s (%d): %s", apiErr.Code, resp.StatusCode, apiErr.Message)
}
return nil, fmt.Errorf("Notion API error (%d): %s", resp.StatusCode, strings.TrimSpace(string(body)))
}
View on GitHub (pinned to 71377f2769)
Solutions
- Match on the code: 'unauthorized' → regenerate the integration token; 'object_not_found' → share the target page/database with the integration.
- For 'rate_limited' (429), retry with exponential backoff respecting the Retry-After header.
- For 'validation_error', compare your request payload against the Notion API docs for that endpoint.
- Confirm the database ID and that your integration has at least read access to it in Notion's UI.
Example fix
// before
resp, err := client.Query(ctx, q) // Notion API error unauthorized (401): API token is invalid.
// after
var apiErr *notion.APIError
if errors.As(err, &apiErr) && apiErr.Code == "rate_limited" {
time.Sleep(retryAfter)
resp, err = client.Query(ctx, q) // retry with backoff
} Defensive patterns
Strategy: try-catch
Validate before calling
// pre-flight check
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.notion.com/v1/users/me", nil)
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Notion-Version", "2022-06-28")
resp, err := http.DefaultClient.Do(req)
if err == nil && resp.StatusCode == 401 {
return fmt.Errorf("notion token invalid")
} Try / catch
var apiErr *notion.APIError
if errors.As(err, &apiErr) {
switch apiErr.Code {
case "unauthorized": // fix token
case "object_not_found": // share DB with integration
case "rate_limited": // backoff and retry
case "validation_error": // fix payload
}
} Prevention
- Share every target database/page with the integration in Notion's UI.
- Rotate tokens via secrets management, not manual edits.
- Respect Retry-After and back off on 429s in bulk operations.
- Validate payloads against the endpoint's documented schema before sending.
When it happens
Trigger: 401 unauthorized (invalid/revoked integration token); 404 object_not_found (page/database ID wrong or not shared with the integration); 400 validation_error (malformed properties/filter); 429 rate_limited (too many requests); 403 restricted_resource.
Common situations: Token rotated/revoked in Notion; database created but never shared with the integration (Notion's connection menu); wrong database ID (missing/extra dashes is fine, but wrong ID 404s); burst-bulk syncs hitting rate limits.
Related errors
- Notion API error (%d): %s
- API error: %s (status %d)
- API error: %s (status %d)
- Notion token not configured
- ado.pat not configured: set via 'bd config set ado.pat <toke
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/d2eaa24dcc60e138.
Report an issue: GitHub.