instructure/canvas-lms · error · InvalidResponse
TII returned # code, content length=# , message # , body #
Error message
TII returned #{resp.status} code, content length=#{body&.length}, message #{error_msg}, body #{body&.truncate(100).inspect} What it means
The Turnitin outcomes response transformer raises InvalidResponse when the upstream TII API reply is not a usable success: error_msg or a bad HTTP status indicates failure. It embeds status code, body length, error message, and a truncated body for diagnostics, after emitting a statsd metric.
Solutions
- Check TII service status and retry once the upstream recovers
- Verify Turnitin account credentials and API endpoint configuration
- Inspect the logged body snippet and statsd tags (lti.tii.outcomes_response_bad) to identify the upstream error
- Add retry/backoff handling around TII outcomes calls for transient 5xx responses
Example fix
// before
resp = transformer.response // raises InvalidResponse on TII failure
// after
begin
resp = transformer.response
rescue TurnitinApi::InvalidResponse => e
logger.warn("TII failed: #{e.message}")
retry with backoff
end Defensive patterns
Strategy: retry
Validate before calling
if (!resp.success?) {
logger.warn(`TII bad status ${resp.status}, deferring`);
return schedule_retry();
} Try / catch
begin
transformer.response
rescue TurnitinApi::InvalidResponse => e
Statsd.increment('tii.failure')
retry with backoff or surface to user
end Prevention
- Monitor the lti.tii.outcomes_response_bad metric
- Validate TII credentials and endpoint config regularly
- Implement exponential backoff for transient upstream failures
- Alert on sustained TII error rates
When it happens
Trigger: TII API returning non-success HTTP status; response missing expected fields (error_msg defaults to :unknown); empty or malformed body from the Turnitin service.
Common situations: Turnitin service outage or degradation; invalid/expired TII API credentials causing auth failures; network proxies returning HTML error pages; TII integration misconfiguration in account settings.
Related errors
- Content-Type must be 'application/xml'
- could not retrieve configuration, the server response timed…
- Couldn't send LTI AGS grade progress metric
- Fetching data from #
- Unable to fetch export data from #
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/a227369fdae54d0a.
Report an issue: GitHub.
Appendix: source
Thrown at gems/turnitin_api/lib/turnitin_api/outcomes_response_transformer.rb:62
def initialize(key, secret, lti_params, outcomes_response_json)
@key = key
@secret = secret
@lti_params = lti_params || {}
@outcomes_response_json = outcomes_response_json
end
def response
@response ||= make_call(outcomes_response_json["outcomes_tool_placement_url"]).tap do |resp|
next resp if (200..299).cover?(resp.status)
error_msg = KNOWN_ERROR_MESSAGES.find { |_name, text| resp.body&.include?(text) }&.first
error_msg ||= :unknown
stats_tags = { status: resp.status, message: error_msg }
InstStatsd::Statsd.distributed_increment("lti.tii.outcomes_response_bad", tags: stats_tags)
body = resp.env[:raw_body] || resp.body
raise InvalidResponse,
"TII returned #{resp.status} code, content length=#{body&.length}, " \
"message #{error_msg}, body #{body&.truncate(100).inspect}"
end
end
# download original
def original_submission
yield make_call(response.body["outcome_originalfile"]["launch_url"])
end
# store link to report
def originality_report_url
response.body["outcome_originalityreport"]["launch_url"]
end
def originality_data
response.body["outcome_originalityreport"].slice("breakdown", "numeric")
endView on GitHub (pinned to 1c9f0bb801)