<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Oleksiy Savich</title>
    <description>The latest articles on DEV Community by Oleksiy Savich (@oleksiysavich).</description>
    <link>https://dev.to/oleksiysavich</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4215144%2F17a0b1b8-cc77-47d2-84eb-55479d547b8a.png</url>
      <title>DEV Community: Oleksiy Savich</title>
      <link>https://dev.to/oleksiysavich</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/oleksiysavich"/>
    <language>en</language>
    <item>
      <title>Convert files from the command line with a free REST API (curl and Python)</title>
      <dc:creator>Oleksiy Savich</dc:creator>
      <pubDate>Thu, 29 Oct 2026 11:49:35 +0000</pubDate>
      <link>https://dev.to/oleksiysavich/convert-files-from-the-command-line-with-a-free-rest-api-curl-and-python-91h</link>
      <guid>https://dev.to/oleksiysavich/convert-files-from-the-command-line-with-a-free-rest-api-curl-and-python-91h</guid>
      <description>&lt;p&gt;I'm Oleksiy, a solo developer from Kyiv. I run CleverUtils, a free online file converter. People kept asking whether they could use it from scripts instead of the browser, so there is now a public REST API. This post shows how I use it from a terminal: one-off conversions with curl, a small Python script with polling and rate-limit handling, and batch jobs. I also cover the limits and the cases where you should skip the API and run FFmpeg or ImageMagick yourself.&lt;/p&gt;

&lt;p&gt;Everything below comes from the &lt;a href="https://cleverutils.com/api" rel="noopener noreferrer"&gt;CleverUtils API docs&lt;/a&gt;. If something here disagrees with the docs, trust the docs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Base URL: &lt;code&gt;https://cleverutils.com/api/v1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;No signup and no API key. Rate limits are per IP address.&lt;/li&gt;
&lt;li&gt;Upload limit: 200 MB per file (the website allows 100 MB).&lt;/li&gt;
&lt;li&gt;Converted files are deleted 2 hours after conversion, so download them right away.&lt;/li&gt;
&lt;li&gt;Responses are JSON with a &lt;code&gt;data&lt;/code&gt; envelope.&lt;/li&gt;
&lt;li&gt;CORS is open (&lt;code&gt;Access-Control-Allow-Origin: *&lt;/code&gt;), so it also works from a browser page.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Your first conversion with curl
&lt;/h2&gt;

&lt;p&gt;Converting an iPhone HEIC photo to JPG is one multipart POST. You send the file and a &lt;code&gt;to_format&lt;/code&gt; value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://cleverutils.com/api/v1/convert &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"file=@photo.heic"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"to_format=jpg"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"job_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"5f8c1a2e9d4b7c0e3f6a8b2d1e4c5f78"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"done"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"output"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"filename"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"photo_cleverutils.com.jpg"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"size_bytes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;184523&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"size_human"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"180 KB"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://cleverutils.com/api/v1/jobs/5f8c.../output"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"links"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"self"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://cleverutils.com/api/v1/jobs/5f8c..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"output"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://cleverutils.com/api/v1/jobs/5f8c.../output"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"expires_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-04-10T14:32:11Z"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then download the result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-o&lt;/span&gt; photo.jpg https://cleverutils.com/api/v1/jobs/&amp;lt;job_id&amp;gt;/output
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;jq&lt;/code&gt; you can chain both steps into one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://cleverutils.com/api/v1/convert &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"file=@photo.heic"&lt;/span&gt; &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"to_format=jpg"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.data.output.url'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
curl &lt;span class="nt"&gt;-o&lt;/span&gt; photo.jpg &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;to_format&lt;/code&gt; is just the target extension: &lt;code&gt;png&lt;/code&gt;, &lt;code&gt;mp3&lt;/code&gt;, &lt;code&gt;pdf&lt;/code&gt;, &lt;code&gt;webp&lt;/code&gt; and so on. The same endpoint handles images, audio, video, documents, archives, fonts and 3D formats.&lt;/p&gt;

&lt;h2&gt;
  
  
  Useful optional parameters
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;/api/v1/convert&lt;/code&gt; takes a few optional fields. The ones I use most:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;img_quality&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0-100, default 80. Used for JPG, WebP and PDF compression&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;img_resize_w&lt;/code&gt;, &lt;code&gt;img_resize_h&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Resize to exact dimensions (16-7680 px), one or both&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;img_rotate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;90&lt;/code&gt;, &lt;code&gt;180&lt;/code&gt;, &lt;code&gt;270&lt;/code&gt;, &lt;code&gt;flip-h&lt;/code&gt;, &lt;code&gt;flip-v&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;img_crop_x/y/w/h&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Crop region in pixels&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;mp3_mode&lt;/code&gt;, &lt;code&gt;mp3_quality&lt;/code&gt;, &lt;code&gt;mp3_bitrate&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;MP3 encoder options&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;wav_rate&lt;/code&gt;, &lt;code&gt;wav_depth&lt;/code&gt;, &lt;code&gt;wav_channels&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;WAV output options&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ocr_lang&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tesseract language for OCR, default &lt;code&gt;eng&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For example, to make a smaller WebP thumbnail:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://cleverutils.com/api/v1/convert &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"file=@banner.png"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"to_format=webp"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"img_quality=70"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"img_resize_w=800"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Status, polling and errors
&lt;/h2&gt;

&lt;p&gt;Small files usually come back with &lt;code&gt;"status": "done"&lt;/code&gt;. Bigger jobs such as long videos can return &lt;code&gt;"processing"&lt;/code&gt;, sometimes with a &lt;code&gt;progress&lt;/code&gt; value from 0 to 99. You poll:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://cleverutils.com/api/v1/jobs/&amp;lt;job_id&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;According to the reference, a job ends up in one of these states:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;status: "processing"&lt;/code&gt;: still running&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;status: "done"&lt;/code&gt;: the &lt;code&gt;output&lt;/code&gt; block is filled in&lt;/li&gt;
&lt;li&gt;HTTP 404 with &lt;code&gt;error.code: JOB_NOT_FOUND&lt;/code&gt;: wrong ID or the job expired&lt;/li&gt;
&lt;li&gt;HTTP 500 with &lt;code&gt;error.code: CONVERSION_FAILED&lt;/code&gt;: the conversion crashed&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A Python script I actually use
&lt;/h2&gt;

&lt;p&gt;The quickstart's Python example is four lines with &lt;code&gt;requests&lt;/code&gt;. The version below adds polling, a download step and basic 429 handling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;API&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://cleverutils.com/api/v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to_format&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;API&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/convert&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;to_format&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;to_format&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;wait&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Retry-After&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to_format&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;job&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;processing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;job&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;API&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/jobs/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;job_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;filename&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;filename&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Usage: &lt;code&gt;python convert.py report.docx pdf&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Two notes. Polling every 2 seconds stays well clear of the 1-second cooldown. For production code, put a retry cap on that 429 branch so it can't loop forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  Batch: up to 20 files per request
&lt;/h2&gt;

&lt;p&gt;For a folder of photos, &lt;code&gt;POST /api/v1/batch&lt;/code&gt; accepts up to 20 files in one multipart request, all converted to the same &lt;code&gt;to_format&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://cleverutils.com/api/v1/batch &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"files[]=@IMG_0001.heic"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"files[]=@IMG_0002.heic"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"files[]=@IMG_0003.heic"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"to_format=jpg"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You get back a &lt;code&gt;batch_id&lt;/code&gt; (format &lt;code&gt;bat_&lt;/code&gt; plus 16 hex characters) and an array of per-file jobs. Poll the whole batch with &lt;code&gt;GET /api/v1/batches/{batch_id}&lt;/code&gt;. It returns &lt;code&gt;overall_status&lt;/code&gt; (&lt;code&gt;done&lt;/code&gt;, &lt;code&gt;processing&lt;/code&gt; or &lt;code&gt;failed&lt;/code&gt;) plus each job, and you download each file from its job's output URL. A batch saves round trips, but every file still counts against your daily quota.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools that aren't a format swap
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;POST /api/v1/tools/{slug}&lt;/code&gt; covers jobs that don't fit "A to B". A few examples from the reference: &lt;code&gt;compress-pdf&lt;/code&gt;, &lt;code&gt;merge-pdf&lt;/code&gt; (send &lt;code&gt;files[]&lt;/code&gt;), &lt;code&gt;split-pdf&lt;/code&gt;, &lt;code&gt;extract-audio-from-video&lt;/code&gt;, &lt;code&gt;trim-video&lt;/code&gt;, &lt;code&gt;resize-image&lt;/code&gt;, and AI tools like &lt;code&gt;remove-background&lt;/code&gt;, &lt;code&gt;upscale-image&lt;/code&gt; and &lt;code&gt;speech-to-text&lt;/code&gt; (options &lt;code&gt;format&lt;/code&gt; = txt/srt/vtt, &lt;code&gt;quality&lt;/code&gt;, &lt;code&gt;language&lt;/code&gt;). Each tool has its own parameters, so check the reference before you use one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rate limits, honestly
&lt;/h2&gt;

&lt;p&gt;There's no key, so limits are per IP. The defaults from the rate-limits page:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1-second cooldown between requests&lt;/li&gt;
&lt;li&gt;20 requests per rolling 60 seconds&lt;/li&gt;
&lt;li&gt;150 per hour, 1000 per day&lt;/li&gt;
&lt;li&gt;AI tools: 50 or 100 per day depending on the tool. &lt;code&gt;upscale-image&lt;/code&gt; also has a 20 MB internal limit&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every response includes &lt;code&gt;X-RateLimit-Remaining-Day&lt;/code&gt;, &lt;code&gt;X-RateLimit-Remaining-Hour&lt;/code&gt; and related headers. &lt;code&gt;GET /api/v1/limits&lt;/code&gt; shows your usage without counting against the quota, and &lt;code&gt;GET /api/v1/health&lt;/code&gt; is a free liveness check. If you go over a limit you get a &lt;code&gt;429&lt;/code&gt; with &lt;code&gt;Retry-After&lt;/code&gt; and an &lt;code&gt;X-RateLimit-Reason&lt;/code&gt; header. Honor it: 10 denials within an hour gets your IP a 24-hour ban (HTTP 403, &lt;code&gt;BANNED&lt;/code&gt;). If you're on a shared IP, like an office or CI runner, keep that in mind.&lt;/p&gt;

&lt;h2&gt;
  
  
  When local FFmpeg or ImageMagick is the better choice
&lt;/h2&gt;

&lt;p&gt;The API runs on the same open-source engines you can install yourself: FFmpeg, ImageMagick, LibreOffice, Ghostscript and Tesseract. That makes the trade-off simple. Use local tools when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Files are sensitive.&lt;/strong&gt; Contracts, medical scans, anything under NDA. Deletion after 2 hours is still an upload to someone else's server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Volume is high.&lt;/strong&gt; Thousands of files a day will hit the 1000/day cap. A local &lt;code&gt;ffmpeg&lt;/code&gt; loop has no quota.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Files are huge.&lt;/strong&gt; Anything over 200 MB is out of scope.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You need exact control.&lt;/strong&gt; Specific codecs, filter graphs, color profiles or two-pass encodes. The API exposes a curated subset of options, not the full FFmpeg command line.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You're offline or latency matters.&lt;/strong&gt; Uploading a 150 MB video to resize it is slower than doing it on your own machine.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The API makes sense when installing the toolchain is the annoying part: a small VPS without LibreOffice, a serverless function, a browser-only page, a quick script on a borrowed laptop, or a one-off conversion like turning a few iPhone photos into JPGs (the &lt;a href="https://cleverutils.com/heic-to-jpg" rel="noopener noreferrer"&gt;HEIC to JPG converter&lt;/a&gt; does the same thing in a browser).&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;That's the whole workflow: POST a file with &lt;code&gt;to_format&lt;/code&gt;, poll if it's still processing, then download within 2 hours. The full endpoint list, error codes and an OpenAPI spec are in the docs. If something breaks or you need a format that isn't supported, there's a contact form on the site, and I read every message myself.&lt;/p&gt;

</description>
      <category>api</category>
      <category>python</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
