instructure/canvas-lms · error

Unable to fetch export data from #

Error message

Unable to fetch export data from #{@status_url}: #{response.code} #{response.message}. (#{response.body})

What it means

LTI content migration via ContentMigrationService polls the tool's status URL for export results. retrieve_export fetches @fetch_url (derived from @status_url) via CanvasHttp and raises this error when the HTTP response is anything other than 200, embedding the status code, reason phrase, and response body for diagnosis. A Timeout::Error becomes a separate tool-specific timeout message.

Solutions

  1. Read the embedded response.body in the message — it usually states whether the export was not found, unauthorized, or a tool-side error
  2. Confirm @status_url/@fetch_url and base_request_headers (OAuth signing, keys) are correct for the current tool configuration
  3. Retry the migration: re-run start! to get a fresh export if the tool reports the job expired or not found
  4. Check the LTI tool's server logs for the corresponding request to identify 4xx vs 5xx root cause
  5. Add retry/backoff around CanvasHttp.get for transient 5xx/502/503 gateway errors

Example fix

// before
response = CanvasHttp.get(@fetch_url, base_request_headers)
raise "Unable to fetch export data from #{@status_url}: ..." unless response.code.to_i == 200
// after
response = CanvasHttp.get(@fetch_url, base_request_headers)
if response.code.to_i == 200
  JSON.parse(response.body)
elsif %w[429 502 503 504].include?(response.code)
  retry_later_with_backoff
else
  raise "Unable to fetch export data from #{@status_url}: ..."
end
Defensive patterns

Strategy: retry

Try / catch

begin
  data = exporter.retrieve_export
rescue RuntimeError => e
  raise unless e.message.start_with?('Unable to fetch export data')
  if e.message =~ /\b(502|503|504|429)\b/
    retry_with_backoff
  else
    mark_migration_failed(e.message)
  end
end

Prevention

When it happens

Trigger: The external LTI tool returns 4xx/5xx on the export status endpoint: expired/invalid signed request, export not found (job expired or already deleted), tool-side crash (500), or a proxy/gateway error between Canvas and the tool.

Common situations: Tool's export expired because the migration sat in queue too long; OAuth/signature mismatch after rotating tool credentials; tool deployed behind a CDN that returns 403/504; misconfigured @status_url pointing at the wrong host.

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 instructure/canvas-lms@1c9f0bb801 (2026-09-15). Data as JSON: /api/errors/706e6806641ad88b. Report an issue: GitHub.

Appendix: source

Thrown at app/models/lti/content_migration_service/exporter.rb:73

            false
          end
        end
      rescue Timeout::Error
        false
      end

      def retrieve_export
        return nil if @export_status == FAILED_STATUS

        response = Canvas.retriable(on: Timeout::Error) do
          InstrumentTLSCiphers.without_tls_metrics do
            CanvasHttp.get(@fetch_url, base_request_headers)
          end
        end
        if response.code.to_i == 200
          JSON.parse(response.body)
        else
          raise "Unable to fetch export data from #{@status_url}: #{response.code} #{response.message}. (#{response.body})"
        end
      rescue Timeout::Error
        raise "Fetching data from #{@tool.name} timed out."
      end

      def start!
        return if defined? @status_url

        InstrumentTLSCiphers.without_tls_metrics do
          response = Canvas.retriable(on: Timeout::Error) do
            case export_format
            when JSON_FORMAT
              CanvasHttp.post(export_start_url, base_request_headers, body: start_export_post_body.to_json, content_type: "application/json")
            else
              CanvasHttp.post(export_start_url, base_request_headers, form_data: Rack::Utils.build_nested_query(start_export_post_body))
            end
          end
          case response.code.to_i

View on GitHub (pinned to 1c9f0bb801)