Tencent/WeKnora · warning
rate limited: %s
Error message
rate limited: %s
What it means
The Notion API returned HTTP 429 (Too Many Requests). The client honors the Retry-After header (defaulting to 1s) and retries up to maxRetries=3 times. This error only escapes when all retry attempts are exhausted; it is then wrapped as `fmt.Errorf("%w: %v", datasource.ErrFetchFailed, lastErr)` before returning, so callers see the wrapped message. The body of the 429 response is embedded in the message.
Source
Thrown at internal/datasource/connector/notion/client.go:126
switch {
case resp.StatusCode >= 200 && resp.StatusCode < 300:
return respBody, nil
case resp.StatusCode == 401 || resp.StatusCode == 403:
return nil, fmt.Errorf("%w: %s", datasource.ErrInvalidCredentials, string(respBody))
case resp.StatusCode == 404:
return nil, fmt.Errorf("%w: %s", datasource.ErrResourceNotFound, path)
case resp.StatusCode == 429:
retryAfter := resp.Header.Get("Retry-After")
wait := 1 * time.Second
if secs, err := strconv.ParseFloat(retryAfter, 64); err == nil && secs > 0 {
wait = time.Duration(secs * float64(time.Second))
}
logger.Warnf(ctx, "[Notion] rate limited, retry after %v (attempt %d/%d)", wait, attempt+1, maxRetries)
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))View on GitHub (pinned to 988cbb0330)
Solutions
- Wait and retry the sync later — the error means the token's Notion rate budget is exhausted for now.
- Reduce concurrency: run one sync at a time per integration token, and avoid sharing one token across multiple jobs or services.
- Lower the client's rate limit (rate.NewLimiter(rate.Limit(3), 3) in newClient, client.go:40) to leave headroom under Notion's ~3 req/s limit.
- Increase maxRetries (client.go:45) if sustained bursts are expected, so long Retry-After waits are honored.
- For repeatedly-hit limits on large workspaces, paginate with smaller page_size and add backoff between pages in the caller.
Example fix
// before: shared token across parallel workers hitting 429 // after: single-flight sync + reduced limiter limiter: rate.NewLimiter(rate.Limit(2), 2), // was rate.Limit(3), 3
Defensive patterns
Strategy: retry
Validate before calling
// Check client-side budget before starting a large sync
func canSync(limiter *rate.Limiter) bool { return limiter.Allow() } Try / catch
if err := client.Ping(ctx); err != nil {
var fetchErr *datasource.FetchError
if strings.Contains(err.Error(), "rate limited") {
time.Sleep(30 * time.Second)
// re-enqueue the job instead of failing the sync
}
_ = fetchErr
} Prevention
- One sync job at a time per Notion integration token
- Keep the client limiter (3 req/s) at or below Notion's documented limit
- Avoid sharing one integration token across multiple services/environments
- Schedule large workspace initial syncs off-peak
When it happens
Trigger: Any doRequest call (Ping, GetPage, GetDatabaseInfo, GetDataSourceInfo, GetBlockChildrenFlat, getBlockChildrenRecursive) that receives 429 on attempt 0, 1, 2, AND 3 — i.e. Notion keeps rate limiting for longer than the Retry-After durations (~1s each, plus the 3 req/s local limiter).
Common situations: Bulk initial sync of a large workspace where many pages/databases are fetched back-to-back; multiple WeKnora workers sharing the same Notion integration token (limits are per-token per-workspace); Retry-After headers of several seconds or minutes exceeding the retry budget.
Related errors
- server error %d: %s
- unmarshal page: %w
- download failed with status %d
- duckduckgo API returned status %d: %s
- model is currently downloading
AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02).
Data as JSON: /api/errors/c39a72d3af79823b.
Report an issue: GitHub.