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

  1. Check TII service status and retry once the upstream recovers
  2. Verify Turnitin account credentials and API endpoint configuration
  3. Inspect the logged body snippet and statsd tags (lti.tii.outcomes_response_bad) to identify the upstream error
  4. 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

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


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")
    end

View on GitHub (pinned to 1c9f0bb801)