{"record":{"id":"706e6806641ad88b","repo":"instructure/canvas-lms","slug":"unable-to-fetch-export-data-from-status-url-response-code","errorCode":null,"errorMessage":"Unable to fetch export data from #{@status_url}: #{response.code} #{response.message}. (#{response.body})","messagePattern":"Unable to fetch export data from #(.+?): #(.+?) #(.+?)\\. \\(#(.+?)\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/models/lti/content_migration_service/exporter.rb","lineNumber":73,"sourceCode":"            false\n          end\n        end\n      rescue Timeout::Error\n        false\n      end\n\n      def retrieve_export\n        return nil if @export_status == FAILED_STATUS\n\n        response = Canvas.retriable(on: Timeout::Error) do\n          InstrumentTLSCiphers.without_tls_metrics do\n            CanvasHttp.get(@fetch_url, base_request_headers)\n          end\n        end\n        if response.code.to_i == 200\n          JSON.parse(response.body)\n        else\n          raise \"Unable to fetch export data from #{@status_url}: #{response.code} #{response.message}. (#{response.body})\"\n        end\n      rescue Timeout::Error\n        raise \"Fetching data from #{@tool.name} timed out.\"\n      end\n\n      def start!\n        return if defined? @status_url\n\n        InstrumentTLSCiphers.without_tls_metrics do\n          response = Canvas.retriable(on: Timeout::Error) do\n            case export_format\n            when JSON_FORMAT\n              CanvasHttp.post(export_start_url, base_request_headers, body: start_export_post_body.to_json, content_type: \"application/json\")\n            else\n              CanvasHttp.post(export_start_url, base_request_headers, form_data: Rack::Utils.build_nested_query(start_export_post_body))\n            end\n          end\n          case response.code.to_i","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/models/lti/content_migration_service/exporter.rb#L55-L91","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Read the embedded response.body in the message — it usually states whether the export was not found, unauthorized, or a tool-side error","Confirm @status_url/@fetch_url and base_request_headers (OAuth signing, keys) are correct for the current tool configuration","Retry the migration: re-run start! to get a fresh export if the tool reports the job expired or not found","Check the LTI tool's server logs for the corresponding request to identify 4xx vs 5xx root cause","Add retry/backoff around CanvasHttp.get for transient 5xx/502/503 gateway errors"],"exampleFix":"// before\nresponse = CanvasHttp.get(@fetch_url, base_request_headers)\nraise \"Unable to fetch export data from #{@status_url}: ...\" unless response.code.to_i == 200\n// after\nresponse = CanvasHttp.get(@fetch_url, base_request_headers)\nif response.code.to_i == 200\n  JSON.parse(response.body)\nelsif %w[429 502 503 504].include?(response.code)\n  retry_later_with_backoff\nelse\n  raise \"Unable to fetch export data from #{@status_url}: ...\"\nend","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"begin\n  data = exporter.retrieve_export\nrescue RuntimeError => e\n  raise unless e.message.start_with?('Unable to fetch export data')\n  if e.message =~ /\\b(502|503|504|429)\\b/\n    retry_with_backoff\n  else\n    mark_migration_failed(e.message)\n  end\nend","preventionTips":["Validate the tool's status_url responds 200 during tool configuration/health checks","Keep signed request headers (OAuth) in sync with the tool's current keys","Set an export TTL retry so stale exports are re-requested via start!","Monitor tool endpoints for 5xx rates before launching migrations"],"tags":["http","lti","network","content-migration"],"backgroundTag":"http-error-response","analyzedSha":"1c9f0bb8013ed69c4f2efe11fd483025469b7e6c","analyzedAt":"2026-09-15T20:33:18.891Z","contentChangedAt":"2026-09-15T20:33:18.891Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}