title: File Download via API using Guzzle
body: <p>I'm still on the learning slopes with Guzzle, but am stumped with this issue...</p>
  <p>I'm trying to retrieve an audio file from a 3rd party API using Guzzle.  I've written a class with a number of functions and they all work apart from the very last stage of the 'download' function - the browser does not prompt to save the audio file</p>
  <p>Call to get audio file, using AJAX</p>
  <pre><code>$callRows.on(&quot;click&quot;, &quot;.download&quot;, function() {
  
      const $this = $(this);
      $this.parent().hide().next().show();
  
      $.ajax({
          type : &quot;POST&quot;,
          url  : &quot;submgmt&quot;,
          data : {
              task   : &quot;getMedia&quot;,
              target : $this.data(&quot;id&quot;)
          },
          success  : function(data) {
  
              $this.parent().show().next().hide();
  
          },
          error : function (XMLHttpRequest, textStatus, errorThrown) {
              $dialog.attr(&quot;title&quot;, textStatus);
              $dialog.html(&quot;&lt;br&gt;&quot; + errorThrown + &quot;&lt;br&gt;&lt;br&gt;&lt;h4&gt;Click OK to continue&lt;/h4&gt;&quot;);
              $dialog.dialog({
                  buttons  : {
                      &quot;OK&quot; : function () {
                          $dialog.dialog(&quot;destroy&quot;);
                      }
                  },
                  dialogClass : &quot;no-close&quot;,
                  modal       : true,
                  resizable   : false
              });
              $this.parent().show().next().hide();
          }
      });
  });
  </code></pre>
  <p><strong>FIRST TRY</strong>
  Function to call download function</p>
  <pre><code>$ucCall = new ucCalls($ucAuth);
  
  try {
  
      $response = $ucCall-&gt;getMedia($target);
  
      if ($response['code']===404) {
          $result['ERROR'] = &quot;1&quot;;
          $result['DETAILS'] = &quot;File is no longer available&quot;;
          echo json_encode($result);
          exit();
      } else {
          header(&quot;Date: &quot; . $response['headers']['Date'][0]);
          header(&quot;Cache-Control: &quot; . $response['headers']['Cache-Control'][0]);
          header(&quot;Pragma: &quot; . $response['headers']['Pragma'][0]);
          header(&quot;Expires: &quot; . $response['headers']['Expires'][0]);
          header(&quot;Content-Type: &quot; . $response['headers']['Content-Type'][0]);
          header('Content-Disposition: attachment; filename=&quot;' . basename($response['filename']) . '&quot;');
          header(&quot;Content-Length: &quot; . filesize($response['filename']));
          ob_clean();
          flush();
          readfile($response['filename']);
          exit;
      }
  } catch (Exception $e) {
      $result['ERROR']   = &quot;1&quot;;
      $result['DETAILS'] = $e-&gt;getCode() . &quot; - &quot; . $e-&gt;getMessage();
      echo json_encode($result);
      exit();
  }
  </code></pre>
  <p>Class to retrieve audio files from 3rd party API</p>
  <pre><code>class ucCalls {
  
  private Client $client;
  
  public function __construct($authString)
  {
      $this-&gt;client = new Client([
          'base_uri' =&gt; 'https://url-of-host',
          'timeout'  =&gt; 5,
          'headers' =&gt; [
              'Authorization' =&gt; 'Basic ' . $authString,
              'Accept' =&gt; 'application/json'
          ],
      ]);
  }
  
  /**
   * Retrieves the unencrypted MP3 media for a (completed) recording
   * @param string $id
   * @return array
   * @throws Exception
   */
  public function getMedia(string $id): array
  {
  
      try {
  
          $temp_file = sys_get_temp_dir() . &quot;\uc&quot; . make_id(6) . &quot;.mp3&quot;;
          $resource = Utils::tryFopen($temp_file, 'w');
  
          $response = $this-&gt;client-&gt;request('GET','fw/path/to/audio/file/store/' . $id . '/Media', ['sink' =&gt; $resource]);
  
          return [
              'code'     =&gt; $response-&gt;getStatusCode(),
              'headers'  =&gt; $response-&gt;getHeaders(),
              'filename' =&gt; $temp_file
          ];
  
      } catch (ConnectException $e) {
          error_log(print_r(&quot;## EXCEPTION ## ucCalls (getMedia) - ConnectException -&gt; &quot; . $e-&gt;getMessage(),true));
          throw new Exception (&quot;Connection Error&quot;,500);
      } catch (ServerException|ClientException $e) {
          error_log(print_r(&quot;## EXCEPTION ## ucCalls (getMedia) - ServerException|ClientException -&gt; &quot; . $e-&gt;getMessage(),true));
          throw new Exception ($e-&gt;getResponse()-&gt;getReasonPhrase(),$e-&gt;getResponse()-&gt;getStatusCode());
      } catch (GuzzleException $e) {
          error_log(print_r(&quot;## EXCEPTION ## ucCalls (getMedia) - GuzzleException -&gt; &quot; . $e-&gt;getMessage(),true));
          throw new Exception (&quot;Unhandled Exception&quot;,400);
      }
  
  }
  </code></pre>
  <p>}</p>
  <p><strong>SECOND TRY</strong> was to try using the Guzzle Stream</p>
  <p>Function to call download function</p>
  <pre><code>$ucCall = new ucCalls($ucAuth);
  
  try {
      
      $ucCall-&gt;getMedia($target);
  
  } catch (Exception $e) {
      $result['ERROR']   = &quot;1&quot;;
      $result['DETAILS'] = $e-&gt;getCode() . &quot; - &quot; . $e-&gt;getMessage();
      echo json_encode($result);
      exit();
  }
  </code></pre>
  <p>Class</p>
  <pre><code>public function getMedia(string $id)
  {
  
      try {
  
          $temp_file = sys_get_temp_dir() . &quot;\uc&quot; . make_id(6) . &quot;.mp3&quot;;
          $resource = Utils::tryFopen($temp_file, 'w+');
          $stream = Utils::streamFor($resource);
  
          $this-&gt;client-&gt;request('GET','fw/path/to/audio/file/store/' . $id . '/Media', ['sink' =&gt; $stream]);
  
          header(&quot;Cache-Control: no-cache&quot;);
          header(&quot;Content-Type: audio/mp3&quot;);
          header('Content-Disposition: attachment; filename=&quot;' . basename($stream-&gt;getMetadata('uri')) . '&quot;');
          header('Content-Length: ' . filesize($stream-&gt;getMetadata('uri')));
  
          echo $stream;
  
      } catch (ConnectException $e) {
          error_log(print_r(&quot;## EXCEPTION ## ucCalls (getMedia) - ConnectException -&gt; &quot; . $e-&gt;getMessage(),true));
          throw new Exception (&quot;Connection Error&quot;,500);
      } catch (ServerException|ClientException $e) {
          error_log(print_r(&quot;## EXCEPTION ## ucCalls (getMedia) - ServerException|ClientException -&gt; &quot; . $e-&gt;getMessage(),true));
          throw new Exception ($e-&gt;getResponse()-&gt;getReasonPhrase(),$e-&gt;getResponse()-&gt;getStatusCode());
      } catch (GuzzleException $e) {
          error_log(print_r(&quot;## EXCEPTION ## ucCalls (getMedia) - GuzzleException -&gt; &quot; . $e-&gt;getMessage(),true));
          throw new Exception (&quot;Unhandled Exception&quot;,400);
      }
  }
  </code></pre>
  <p>Both these functions create the audio file in a temp store on my local web server, so the audio file retrieval from the 3rd party works.  This is where I get stumped...</p>
  <p>The file appears to transfer to the client, the header attributes are recognised and the correct amount of data is transfer (filesize) - but the client browser (tested in Edge and Firefox) is not prompting to save the file.  Just getting an encoded string, as per the screenshots...</p>
  <p><a href="https://i.stack.imgur.com/qUFzr.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/qUFzr.png" alt="file created on local server" /></a></p>
  <p><a href="https://i.stack.imgur.com/d1Q3i.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/d1Q3i.png" alt="headers on response from attempt 1" /></a></p>
  <p><a href="https://i.stack.imgur.com/ZoJ2C.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/ZoJ2C.png" alt="headers on response from attempt 2" /></a></p>
  <p><a href="https://i.stack.imgur.com/KknKz.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/KknKz.png" alt="server response from both requests identical" /></a></p>
  <p>Any thoughts or guidance would be appreciated...</p>