Freika/dawarich · error · AirTrail::Client::Error

AirTrail returned an unsuccessful response

Error message

AirTrail returned an unsuccessful response

What it means

After a 2xx response, the AirTrail client parses the body and expects the envelope {success: true, flights: [...]}. If body['success'] is falsy, it raises 'AirTrail returned an unsuccessful response' - the HTTP layer was fine but the application layer inside AirTrail refused the operation. This is a 200-with-error-envelope contract, so the HTTP code alone cannot diagnose it.

Source

Thrown at app/services/air_trail/client.rb:30

      @skip_ssl_verification = skip_ssl_verification
    end

    def flights(scope: 'mine')
      response = HTTParty.get(
        "#{@url}/api/flight/list?scope=#{scope}",
        http_options_with_ssl_flag(@skip_ssl_verification, {
                                     headers: {
                                       'Authorization' => "Bearer #{@api_key}",
                                       'accept' => 'application/json'
                                     },
                                     timeout: 15
                                   })
      )

      raise Error, "AirTrail responded with #{response.code}" unless response.success?

      body = JSON.parse(response.body)
      raise Error, 'AirTrail returned an unsuccessful response' unless body['success']

      body['flights'] || []
    rescue HTTParty::Error, Net::OpenTimeout, Net::ReadTimeout, JSON::ParserError => e
      raise Error, e.message
    end
  end
end

View on GitHub (pinned to 97fad417c5)

Solutions

  1. Inspect the raw AirTrail response body (log response.body temporarily) - the reason for success:false is usually inside it
  2. Verify the AirTrail version's /api/flight/list contract still uses {success, flights}
  3. Confirm the API key's account can read the requested scope
  4. Try scope variations against the instance directly with curl

Example fix

# before
body = JSON.parse(response.body)
raise Error, 'AirTrail returned an unsuccessful response' unless body['success']

# after (surface the reason without leaking secrets)
body = JSON.parse(response.body)
raise Error, "AirTrail returned an unsuccessful response: #{body['error'] || body['message']}" unless body['success']
Defensive patterns

Strategy: try-catch

Try / catch

begin
  client.flights
rescue AirTrail::Error => e
  Sentry.capture_message('airtrail_envelope_failure', level: :warning, extra: { message: e.message })
  raise
end

Prevention

When it happens

Trigger: AirTrail returning 200 with {success: false} - e.g. the key authenticates but the account has no flight permission for the requested scope, an internal AirTrail error surfaced as success:false, or an API version whose envelope semantics changed (success field absent also counts as falsy).

Common situations: AirTrail version drift changing the response envelope (dropping or renaming 'success'), scope values (mine vs all) the instance rejects at app level, partially-deployed AirTrail instances answering with error JSON behind a 200, rate-limit style responses wrapped in 200.

Related errors


AI-assisted analysis of Freika/dawarich@97fad417c5 (2026-08-21). Data as JSON: /api/errors/3fe303524e2362d9. Report an issue: GitHub.