Search code examples
ruby-on-railsxmlapipostyahoo-api

Yahoo Fantasy Sports API routing/post errors


As a quick background: I've already successfully set up a small application that will connect to Yahoo's API and set my lineups on a daily basis using PUT requests on my team's roster per the Yahoo Developer's Guide.

Specifically, where I'm having problems now:

POST

Using POST, players can be added and/or dropped from a team, or trades can be proposed. The URI for POSTing to transactions collection is:

http://fantasysports.yahooapis.com/fantasy/v2/league//transactions

The input XML format for a POST request to the transactions API for replacing one player with another player in a team is:

<fantasy_content>
  <transaction>
    <type>add/drop</type>
    <players>
      <player>
        <player_key>{player_key}</player_key>
        <transaction_data>
          <type>add</type>
          <destination_team_key>{team_key}</destination_team_key>
        </transaction_data>
      </player>
      <player>
        <player_key>{player_key}</player_key>
        <transaction_data>
          <type>drop</type>
          <source_team_key>{team_key}</source_team_key>
        </transaction_data>
      </player>
    </players>
  </transaction>
</fantasy_content>

My method for making a request is as follows:

  def self.make_signed_request(address, method, user_id, input_file=nil )

    # format http method and return false if not accepted API method
    method = method.upcase
    return false if ( method != "GET" ) && ( method != "POST" ) && ( method != "PUT")    

    # find user
    user = User.find(user_id)

    if ( user.yahoo_access_token_expiration.nil? || user.yahoo_access_token_expiration < Time.now )
      # expired token, so renew
      YahooOAuth.renew_token(user_id)
      user.reload
    end

    # normalize text HMAC-SHA1
    digest = OpenSSL::Digest.new('sha1') 
    nonce = rand(10 ** 30).to_s.rjust(30,'0')
    timestamp = Time.now.to_i
    text = method + "&" + CGI.escape("#{address}") + "&" + CGI.escape("oauth_consumer_key=#{YAHOO_OAUTH_API[:CLIENT_ID]}&oauth_nonce=#{nonce}&oauth_signature_method=HMAC-SHA1&oauth_timestamp=#{timestamp}&oauth_token=#{CGI.escape(user.yahoo_access_token)}&oauth_version=1.0")

    # create key for HMAC-SHA1
    key = CGI.escape("#{YAHOO_OAUTH_API[:CLIENT_SECRET]}")+ "&" + CGI.escape("#{user.yahoo_access_token_secret}")

    # create HMAC-SHA1 signature for api request
    hmac = OpenSSL::HMAC.digest(digest, key, text)
    signature_sha1 = CGI.escape(Base64.strict_encode64(hmac))    

    # make API request
    response = nil
    if method == "GET"
      response = Curl.get("#{address}?oauth_consumer_key=#{YAHOO_OAUTH_API[:CLIENT_ID]}&oauth_nonce=#{nonce}&oauth_signature_method=HMAC-SHA1&oauth_timestamp=#{timestamp}&oauth_token=#{CGI.escape(user.yahoo_access_token)}&oauth_version=1.0&oauth_signature=#{signature_sha1}")  
    elsif method == "POST"
      # return "Not implemented"
      response = Curl.post("#{address}?oauth_consumer_key=#{YAHOO_OAUTH_API[:CLIENT_ID]}&oauth_nonce=#{nonce}&oauth_signature_method=HMAC-SHA1&oauth_timestamp=#{timestamp}&oauth_token=#{CGI.escape(user.yahoo_access_token)}&oauth_version=1.0&oauth_signature=#{signature_sha1}", input_file) do |http|
        http.headers['Accept'] = 'application/xml'
        http.headers['Content-Type'] = 'application/xml'
      end
    elsif method == "PUT"
      response = Curl.put("#{address}?oauth_consumer_key=#{YAHOO_OAUTH_API[:CLIENT_ID]}&oauth_nonce=#{nonce}&oauth_signature_method=HMAC-SHA1&oauth_timestamp=#{timestamp}&oauth_token=#{CGI.escape(user.yahoo_access_token)}&oauth_version=1.0&oauth_signature=#{signature_sha1}", input_file) do |http|
        http.headers['Accept'] = 'application/xml'
        http.headers['Content-Type'] = 'application/xml'
      end
    end

    # return raw XML result
    return response.body

  end

When I make my request --

def add_drop(players)
    # players are added in [{player_key}, {add/drop}, {faab_bid (or nil if not FAAB)}] form
    url = "http://fantasysports.yahooapis.com/fantasy/v2/league/#{self.league.league_key}/transactions"
    builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
      xml.fantasy_content {
        xml.transaction {
          xml.type "add/drop"
          xml.faab_bid players[0][2] unless players[0][2].nil?
          xml.players {
            players.each do |player|
              xml.player {
                xml.player_key player[0]
                xml.transaction_data {
                  xml.type player[1] # this will be "add" or "drop"
                  # adds use "destination_team_key," drops use "source_team_key"
                  if player[1] == "add"
                    xml.destination_team_key self.team_key
                  else
                    xml.source_team_key self.team_key
                  end
                } # transaction_data
              } # player
            end
          } # players
        } # transaction
      } # fantasy_content
    end # builder 
    xml_input = builder.to_xml
    YahooOAuth.make_signed_request(url, 'put', self.user.id, xml_input)
  end

--the XML generated is shown below --

"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<fantasy_content>\n  <transaction>\n    <type>add/drop</type>\n    <players>\n      <player>\n        <player_key>357.p.10171</player_key>\n        <transaction_data>\n          <type>add</type>\n          <destination_team_key>370.l.4801.t.7</destination_team_key>\n        </transaction_data>\n      </player>\n      <player>\n        <player_key>357.p.9317</player_key>\n        <transaction_data>\n          <type>drop</type>\n          <source_team_key>370.l.4801.t.7</source_team_key>\n        </transaction_data>\n      </player>\n    </players>\n  </transaction>\n</fantasy_content>\n"

-- and the two responses I'll get from Yahoo are:

> <?xml version="1.0" encoding="UTF-8"?> <error xml:lang="en-us"
> yahoo:uri="http://fantasysports.yahooapis.com/fantasy/v2/league/370.l.4801/transactions/?oauth_consumer_key=dj0yJmk9dHBIa0h4ekhTSVBnJmQ9WVdrOVlUZHFkMnhMTXpJbWNHbzlNQS0tJnM9Y29uc3VtZXJzZWNyZXQmeD1lNQ--&amp;amp;oauth_nonce=892057444475304460343340318332&amp;amp;oauth_signature_method=HMAC-SHA1&amp;amp;oauth_timestamp=1492097395&amp;amp;oauth_token=A%3D2.86iTDxtT4nQ.wxTcn33mgnA8dm3AeME87YRjNthVjxiwfhqKr_oWt0HBgbBeS2DC_dNObN72m0ucgi7CsSFaRjpc5IcnZ_bpJNTC3pUXc37bgH8f0S4irpyXLz8s9ONAYYB.LIDT0sOUvdTgk_lPDnlr1GlPhCe7u54Sq.m_vwq1JQlNUzEVrEs3kOW9wiXk17BditA9OGaVE.tuepvpthDRVBhOye8qjb_ic7UZtT.lvccoGEdgvcShHSyg.YYcnShl7ks23G01hAcXrfGveEk0UncWKNmma42cYbg7bzSOY9ZZj3_hvU5rK3SjB1ADPe8bsIEe_Ba9KBhYxlWd9iyyAR_bloL9n0eeL_OQ6PoR4uGJ6VMUDn9n_ovDGvfgAfvtJs15pCcXPhYusuo1S7SJF1O3fLtR8TitmU1qW88x3SenY2U50dlRG9Y73iNUdnyYBpIHLg._pPkms26QhnuxSFfqpXcGleGXOuZ0.TNOE3Cp8VbLEOEIg6QkavgGLKyFetYhSQ879T4rfhfeEoWvwkjsO1BL2Y3n4Hp9cgfU4y3wZvT.b8qhP7QY0UTYtZkyYH.sydFUXUCec.yVGm29S.s.2N96tfr4qWaI0qntRE.X5MVdwfbhz94n9JshmduqurjKRLlMYVWnLZ_Yderm0HDvT7dnowjyUwBx2UxUKJooauQnNU67QQECmh.HZqcm_OBysLABvdtTtaPhnvJ1QViN_UUjslToVPOs1oyxoTNRbL0VL8yxJc&amp;amp;oauth_version=1.0&amp;amp;oauth_signature=UVmcj2S8c5vqkRgAxsdQ3MQZI54%3D"
> xmlns:yahoo="http://www.yahooapis.com/v1/base.rng"
> xmlns="http://www.yahooapis.com/v1/base.rng"> 
> <description>subresource 'transactions' not supported.</description> 
> <detail/> </error>

and

> <?xml version="1.0" encoding="UTF-8"?> <error xml:lang="en-us"
> yahoo:uri="http://fantasysports.yahooapis.com/fantasy/v2/league/370.l.4801/transactions?oauth_consumer_key=dj0yJmk9dHBIa0h4ekhTSVBnJmQ9WVdrOVlUZHFkMnhMTXpJbWNHbzlNQS0tJnM9Y29uc3VtZXJzZWNyZXQmeD1lNQ--&amp;amp;oauth_nonce=503128074504907304111221170641&amp;amp;oauth_signature_method=HMAC-SHA1&amp;amp;oauth_timestamp=1492097198&amp;amp;oauth_token=A%3D2.86iTDxtT4nQ.wxTcn33mgnA8dm3AeME87YRjNthVjxiwfhqKr_oWt0HBgbBeS2DC_dNObN72m0ucgi7CsSFaRjpc5IcnZ_bpJNTC3pUXc37bgH8f0S4irpyXLz8s9ONAYYB.LIDT0sOUvdTgk_lPDnlr1GlPhCe7u54Sq.m_vwq1JQlNUzEVrEs3kOW9wiXk17BditA9OGaVE.tuepvpthDRVBhOye8qjb_ic7UZtT.lvccoGEdgvcShHSyg.YYcnShl7ks23G01hAcXrfGveEk0UncWKNmma42cYbg7bzSOY9ZZj3_hvU5rK3SjB1ADPe8bsIEe_Ba9KBhYxlWd9iyyAR_bloL9n0eeL_OQ6PoR4uGJ6VMUDn9n_ovDGvfgAfvtJs15pCcXPhYusuo1S7SJF1O3fLtR8TitmU1qW88x3SenY2U50dlRG9Y73iNUdnyYBpIHLg._pPkms26QhnuxSFfqpXcGleGXOuZ0.TNOE3Cp8VbLEOEIg6QkavgGLKyFetYhSQ879T4rfhfeEoWvwkjsO1BL2Y3n4Hp9cgfU4y3wZvT.b8qhP7QY0UTYtZkyYH.sydFUXUCec.yVGm29S.s.2N96tfr4qWaI0qntRE.X5MVdwfbhz94n9JshmduqurjKRLlMYVWnLZ_Yderm0HDvT7dnowjyUwBx2UxUKJooauQnNU67QQECmh.HZqcm_OBysLABvdtTtaPhnvJ1QViN_UUjslToVPOs1oyxoTNRbL0VL8yxJc&amp;amp;oauth_version=1.0&amp;amp;oauth_signature=oNaLute5djkIryUEKq0zF6prVFU%3D"
> xmlns:yahoo="http://www.yahooapis.com/v1/base.rng"
> xmlns="http://www.yahooapis.com/v1/base.rng">  
> <description>Invalid XML POSTed. Error code 25 at line 3, column 0 of input XML.</description>
> <detail/> </error>

I'm honestly not sure what differentiates between the two responses, as I've gotten both responses with what I'm pretty sure are the same XML inputs and POST parameters.

Does anybody have any insight?


Solution

  • Just ran into the same issue, and this Stack Overflow article was the only relevant link I found on the web. Decided it was time I gave back to this community...

    The problem is that one of the sample XMLs on Yahoo's transaction page is wrong. The sample with the { } placeholders and without the <faab_bid> node is correct, but the FAAB example below it has <destination_team_key> instead of <source_team_key> as part of the "drop" transaction. When I made sure to use "source_team_key" instead of "destination_team_key" for the drop set of nodes, the transaction started working.

    I'm guessing that Error Code 25 is a generic error indicating that some part of the XML is wrong or unexpected. If this doesn't resolve your issue, try starting with the first add/drop sample XML and filling in values one by one.

    Hope this helps.

    -Igor