instructure/canvas-lms · warning

You've hit the study tools rate limit. Try again later.

Error message

You've hit the study tools rate limit. Try again later.

What it means

StudyAssistController#create rescues StudyAssist::RateLimited raised by the upstream study-tools/Cedar pipeline and renders a 429 with this message. The client exceeded the configured rate limit for AI study tools prompts within the allowed window.

Solutions

  1. Wait for the rate-limit window to elapse and retry with backoff
  2. Reduce request frequency / batch prompts in the client
  3. Check the rate limit configuration (per user/account) and raise it if legitimately too low
  4. Record which request triggered it via the logged 'Study Assist rate limit' warning and throttle client-side

Example fix

// before
// client retries immediately on failure
for (const p of prompts) await send(p)
// after
for (const p of prompts) {
  const res = await send(p)
  if (res.status === 429) await sleep(Math.pow(2, attempt++) * 1000)
}
Defensive patterns

Strategy: retry

Try / catch

const res = await fetch(url, opts)
if (res.status === 429) {
  await sleep(Math.min(backoffMs *= 2, 60_000))
  return retry()
}

Prevention

When it happens

Trigger: A user or integration POSTs prompts to the study assist create endpoint more times than the per-user/account limit allows in the rate window, causing the service layer to raise StudyAssist::RateLimited.

Common situations: Loops or batch scripts hammering the study assist endpoint; shared accounts consumed by multiple users; classroom-wide usage spikes; aggressively-retrying clients after failures.

Related errors


AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15). Data as JSON: /api/errors/86ac5bb9d01b61bc. Report an issue: GitHub.

Appendix: source

Thrown at app/controllers/study_assist_controller.rb:75

    record_metric("unknown", "unsupported")
    render json: { error: t("Study tools aren't available for this prompt.") }, status: :unprocessable_content
  rescue StudyAssist::ToolDisabled
    record_metric(tool_tag(request_body[:prompt]), "unsupported")
    render json: { error: t("This study tool isn't currently available.") }, status: :forbidden
  rescue StudyAssist::UnsupportedContentType
    record_metric(tool_tag(request_body[:prompt]), "unsupported")
    render json: { error: t("Study tools aren't available for this file type.") }, status: :unprocessable_content
  rescue StudyAssist::ContentUnavailable => e
    record_metric(tool_tag(request_body[:prompt]), "content_unavailable")
    Rails.logger.info("Study Assist content unavailable: #{e.message}")
    render json: { error: t("Content isn't available for study tools.") }, status: :unprocessable_content
  rescue StudyAssist::ContentTooLarge
    record_metric(tool_tag(request_body[:prompt]), "too_large")
    render json: { error: t("Content is too long for study tools.") }, status: :unprocessable_content
  rescue StudyAssist::RateLimited => e
    record_metric(tool_tag(request_body[:prompt]), "rate_limit")
    Rails.logger.warn("Study Assist rate limit: #{e.message}")
    render json: { error: t("You've hit the study tools rate limit. Try again later.") }, status: :too_many_requests
  rescue StudyAssist::CedarUnavailable => e
    record_metric(tool_tag(request_body[:prompt]), "cedar_error")
    Rails.logger.error("Study Assist Cedar failure: #{e.message}")
    render json: { error: t("Study tools are temporarily unavailable. Please try again.") }, status: :bad_gateway
  end

  private

  def require_study_assist_enabled
    return if @context.is_a?(Course) && @context.feature_enabled?(:study_assist)

    render json: { error: t("Study tools aren't enabled for this course.") }, status: :not_found
    false
  end

  def require_student_access
    authorized_action(@context, @current_user, :participate_as_student)
  end

View on GitHub (pinned to 1c9f0bb801)