{"id":2996,"date":"2026-10-27T08:25:09","date_gmt":"2026-10-27T08:25:09","guid":{"rendered":"https:\/\/kb.shortpixel.com\/?post_type=docs&#038;p=2996"},"modified":"2026-10-27T08:28:12","modified_gmt":"2026-10-27T08:28:12","password":"","slug":"ai-image-seo-via-api","status":"publish","type":"docs","link":"https:\/\/kb.shortpixel.com\/knowledge-base\/article\/ai-image-seo-via-api\/","title":{"rendered":"AI Image SEO via API"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Send the URL of a public image and get back SEO metadata for it: title, alt text, caption, long description and, optionally, an SEO-friendly file name. The API works on any website or platform, not only WordPress.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The API is asynchronous and has two calls:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>POST \/add-url.php queues an image and returns a job id.<\/li>\n\n\n\n<li>POST \/get-url.php with that id returns the job status, then the generated fields once it is finished.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">Base URL: https:\/\/capi-gpt.shortpixel.com. All requests and responses are JSON (Content-Type: application\/json).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The image must be reachable over the public internet, because our servers download it. Supported formats: JPG\/JPEG\/JPE, PNG, GIF, WebP, AVIF. Image URLs can be up to 1000 characters long.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\"><strong>Authentication<\/strong><\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Use your ShortPixel API key in the Authorization header. The key comes from your ShortPixel account, and your account plan must include the AI (Image SEO) feature.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Authorization: ApiKey YOUR_API_KEY<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">A call made with an API key returns a session token (JWT) that is valid for 1 hour. You get it in two places: the X-New-JWT response header, and the jwt field of the JSON body (next to user_id). For the next hour you can send that token instead of the key:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Authorization: Bearer YOUR_JWT<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">There is no automatic fallback once the token expires: the call returns HTTP 401, and you have to authenticate again with ApiKey. Integrations that only make a few calls can simply send ApiKey every time.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>One key per site (recommended).<\/strong> Create an alias API key in your ShortPixel account and set its allowed domains. Requests for images on any other domain are then rejected with The domain &lt;domain&gt; is not allowed for this API key. Every image domain you send is also linked to your ShortPixel account. On free accounts, a domain that is already used by other free accounts is rejected.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Keep the key on your server. Never embed it in browser or mobile app code.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\"><strong>Submit an image: POST \/add-url.php<\/strong><\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Send the image URL plus one object for each field you want generated. Only the fields you request are generated. Each object takes a maximum length (chars) and optional instructions (context).<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th><strong>Field<\/strong><\/th><th><strong>Type<\/strong><\/th><th><strong>Required<\/strong><\/th><th><strong>Notes<\/strong><\/th><\/tr><\/thead><tbody><tr><td>url<\/td><td>string<\/td><td>yes<\/td><td>Public image URL, up to 1000 characters<\/td><\/tr><tr><td>version<\/td><td>string<\/td><td>recommended<\/td><td>Send &#8220;v_2&#8221;. Quota is then checked up front, and the call is rejected right away when you are over quota<\/td><\/tr><tr><td>languages<\/td><td>string<\/td><td>no<\/td><td>Output language: ISO 639-1 code (en, de), optionally with a country (pt-BR). Default en<\/td><\/tr><tr><td>context<\/td><td>string<\/td><td>no<\/td><td>General description of the image or page, up to 500 characters<\/td><\/tr><tr><td>title<\/td><td>object<\/td><td>no<\/td><td>Generates title. chars max 100, default 20<\/td><\/tr><tr><td>alt<\/td><td>object<\/td><td>no<\/td><td>Generates alt. chars max 200, default 72<\/td><\/tr><tr><td>caption<\/td><td>object<\/td><td>no<\/td><td>Generates caption. chars max 250, default 128<\/td><\/tr><tr><td>image_description<\/td><td>object<\/td><td>no<\/td><td>Generates image_description. chars max 500, default 300<\/td><\/tr><tr><td>file<\/td><td>object<\/td><td>no<\/td><td>Generates generated_file_name, a lowercase slug without extension. chars 1\u2013100, default 48. Also takes existing_filename and prefer_keep_filename_if_relevant (see below)<\/td><\/tr><tr><td>use_parent_post_title + parent_post_title<\/td><td>bool + string<\/td><td>no<\/td><td>Title of the page that shows the image, used as context. Up to 500 characters<\/td><\/tr><tr><td>use_parent_post_category + parent_post_category_list<\/td><td>bool + string[]<\/td><td>no<\/td><td>Categories or tags of that page, used as context. Up to 50 items<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Each context is capped at 500 characters. chars values above the maximum are reduced to the maximum.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>All fields with their default values.<\/strong> Leave out any object you don&#8217;t need; only url is required.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n\n&nbsp;&nbsp;\"url\": \"https:\/\/example.com\/wp-content\/uploads\/2025\/dog.jpg\",\n\n&nbsp;&nbsp;\"languages\": \"en\",\n\n&nbsp;&nbsp;\"context\": \"optional global context\",\n\n&nbsp;&nbsp;\"version\": \"v_2\",\n\n&nbsp;&nbsp;\"file\": {\n\n&nbsp;&nbsp;&nbsp;&nbsp;\"chars\": 48,\n\n&nbsp;&nbsp;&nbsp;&nbsp;\"context\": \"filename with SEO keywords\",\n\n&nbsp;&nbsp;&nbsp;&nbsp;\"prefer_keep_filename_if_relevant\": true,\n\n&nbsp;&nbsp;&nbsp;&nbsp;\"existing_filename\": \"my-dog-in-garden\"\n\n&nbsp;&nbsp;},\n\n&nbsp;&nbsp;\"title\": {\n\n&nbsp;&nbsp;&nbsp;&nbsp;\"chars\": 20,\n\n&nbsp;&nbsp;&nbsp;&nbsp;\"context\": \"...\"\n\n&nbsp;&nbsp;},\n\n&nbsp;&nbsp;\"alt\": {\n\n&nbsp;&nbsp;&nbsp;&nbsp;\"chars\": 72,\n\n&nbsp;&nbsp;&nbsp;&nbsp;\"context\": \"...\"\n\n&nbsp;&nbsp;},\n\n&nbsp;&nbsp;\"caption\": {\n\n&nbsp;&nbsp;&nbsp;&nbsp;\"chars\": 128,\n\n&nbsp;&nbsp;&nbsp;&nbsp;\"context\": \"...\"\n\n&nbsp;&nbsp;},\n\n&nbsp;&nbsp;\"image_description\": {\n\n&nbsp;&nbsp;&nbsp;&nbsp;\"chars\": 300,\n\n&nbsp;&nbsp;&nbsp;&nbsp;\"context\": \"...\"\n\n&nbsp;&nbsp;},\n\n&nbsp;&nbsp;\"use_parent_post_title\": true,\n\n&nbsp;&nbsp;\"parent_post_title\": \"Dogs in the garden\",\n\n&nbsp;&nbsp;\"use_parent_post_category\": true,\n\n&nbsp;&nbsp;\"parent_post_category_list\": &#91;\"Photography\", \"Dogs\"]\n\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>File name.<\/strong> Pass the current name without its extension in file.existing_filename. When prefer_keep_filename_if_relevant is true, generated_file_name is returned only if the current name is not good for SEO. When the key is missing from the result, keep your current file name.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Example request<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n\n&nbsp;&nbsp;\"url\": \"https:\/\/example.com\/images\/IMG_2041.jpg\",\n\n&nbsp;&nbsp;\"version\": \"v_2\",\n\n&nbsp;&nbsp;\"languages\": \"en\",\n\n&nbsp;&nbsp;\"context\": \"Product photo for an online garden furniture store\",\n\n&nbsp;&nbsp;\"title\": { \"chars\": 60 },\n\n&nbsp;&nbsp;\"alt\": { \"chars\": 120, \"context\": \"describe for accessibility and search\" },\n\n&nbsp;&nbsp;\"caption\": { \"chars\": 150 },\n\n&nbsp;&nbsp;\"image_description\": { \"chars\": 300 },\n\n&nbsp;&nbsp;\"file\": { \"existing_filename\": \"IMG_2041\", \"prefer_keep_filename_if_relevant\": true, \"chars\": 60 },\n\n&nbsp;&nbsp;\"use_parent_post_title\": true,\n\n&nbsp;&nbsp;\"parent_post_title\": \"Teak garden bench, 3 seats\",\n\n&nbsp;&nbsp;\"use_parent_post_category\": true,\n\n&nbsp;&nbsp;\"parent_post_category_list\": &#91;\"Garden furniture\", \"Benches\"]\n\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The job was queued if the response contains an id:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{ \"id\": 3786, \"status\": 0 }<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Otherwise the response holds error and no job was created (see Errors). The response may also include warning when your account is over its fair-usage threshold. The job is still accepted, but it is processed with a delay of 20\u201360 seconds.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\"><strong>Get the result: POST \/get-url.php<\/strong><\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Poll with the job id until status is 2, -1 or -2. All three are final. Start about 5 seconds after submitting, then wait a little longer before each new check. You can only read your own jobs. Any other id returns HTTP 404.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{ \"id\": 3786 }<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th><strong>status<\/strong><\/th><th><strong>Meaning<\/strong><\/th><th><strong>What to do<\/strong><\/th><\/tr><\/thead><tbody><tr><td>0<\/td><td>Queued<\/td><td>Poll again later<\/td><\/tr><tr><td>1<\/td><td>Processing<\/td><td>Poll again later<\/td><\/tr><tr><td>2<\/td><td>Finished: the generated fields are in the response<\/td><td>Save them<\/td><\/tr><tr><td>-1<\/td><td>Failed: the reason is in error (download failed, timeout, quota, &#8230;)<\/td><td>Fix the cause and submit again<\/td><\/tr><tr><td>-2<\/td><td>Image host temporarily blocked (see Errors)<\/td><td>Stop polling. Submit again in about an hour<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">A rejected add-url creates no job. A job that was accepted always ends as 2, -1 or -2.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A finished response contains only the fields you requested:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n\n&nbsp;&nbsp;\"id\": 3786,\n\n&nbsp;&nbsp;\"status\": 2,\n\n&nbsp;&nbsp;\"title\": \"Teak Garden Bench with Three Seats\",\n\n&nbsp;&nbsp;\"alt\": \"Three-seat teak wooden bench on a green lawn\",\n\n&nbsp;&nbsp;\"caption\": \"Our teak bench, built to last outdoors in any season.\",\n\n&nbsp;&nbsp;\"image_description\": \"A natural teak garden bench with slatted seat and backrest ...\",\n\n&nbsp;&nbsp;\"generated_file_name\": \"teak-garden-bench-three-seats\"\n\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Results can be read again as often as you need, for 7 days. After 7 days finished and failed jobs are deleted, so store the fields on your side.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\"><strong>Errors and limits<\/strong><\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Always check the response body for an error key. Business rejections come back as HTTP 200 with {&#8220;error&#8221;: &#8220;&#8230;&#8221;} and no id. Only malformed requests, authentication and unknown jobs use other HTTP codes.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th><strong>HTTP<\/strong><\/th><th><strong>Example<\/strong> <strong>error<\/strong><\/th><th><strong>Cause<\/strong><\/th><\/tr><\/thead><tbody><tr><td>400<\/td><td>Invalid or missing &#8216;url&#8217;. \/ Missing or invalid &#8216;id&#8217;.<\/td><td>Missing or malformed field, or the body is not valid JSON<\/td><\/tr><tr><td>401<\/td><td>Unauthorized<\/td><td>Missing or wrong API key, or expired JWT<\/td><\/tr><tr><td>404<\/td><td>Not found \/ Job not found.<\/td><td>Unknown job, or the job belongs to another account<\/td><\/tr><tr><td>200<\/td><td>Too many jobs in queue. Please wait for some to complete.<\/td><td>You already have 100 jobs waiting<\/td><\/tr><tr><td>200<\/td><td>AI Image SEO quota reached (N images). Upgrade to the Unlimited AI plan.<\/td><td>Plan quota used up (checked up front only with &#8220;version&#8221;: &#8220;v_2&#8221;)<\/td><\/tr><tr><td>200<\/td><td>The domain &lt;domain&gt; is not allowed for this API key.<\/td><td>The image domain is not in the key&#8217;s allow list<\/td><\/tr><tr><td>200<\/td><td>This image host is temporarily blocked &#8230;<\/td><td>Over 90% of recent jobs for that host failed<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Queue limit.<\/strong> Each account can have up to 100 jobs waiting. Submit in batches and poll; send more as jobs finish.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Host block.<\/strong> When at least 90% of the jobs for one image host fail within an hour (minimum 3 jobs), that host is blocked for 1 hour. This usually happens when a site blocks our downloader, for example with a firewall, hotlink protection or a login wall. Jobs already queued for that host switch to status -2.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Quota.<\/strong> Every processed image counts toward the AI Image SEO quota of your plan. Unlimited plans have a fair-usage threshold. Above it, jobs are still processed, but with a delay of 20\u201360 seconds, and add-url returns a warning.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\"><strong>Code examples<\/strong><\/h1>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>curl<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>curl -s -X POST https:\/\/capi-gpt.shortpixel.com\/add-url.php \\\n\n&nbsp;&nbsp;-H \"Authorization: ApiKey $SHORTPIXEL_API_KEY\" \\\n\n&nbsp;&nbsp;-H \"Content-Type: application\/json\" \\\n\n&nbsp;&nbsp;-d '{\"url\":\"https:\/\/example.com\/images\/bench.jpg\",\"version\":\"v_2\",\"languages\":\"en\",\"alt\":{\"chars\":120},\"title\":{\"chars\":60}}'\n\ncurl -s -X POST https:\/\/capi-gpt.shortpixel.com\/get-url.php \\\n\n&nbsp;&nbsp;-H \"Authorization: ApiKey $SHORTPIXEL_API_KEY\" \\\n\n&nbsp;&nbsp;-H \"Content-Type: application\/json\" \\\n\n&nbsp;&nbsp;-d '{\"id\":3786}'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>PHP<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;?php\n\nconst BASE = 'https:\/\/capi-gpt.shortpixel.com';\n\nfunction capi(string $path, array $body): array {\n\n&nbsp;&nbsp;&nbsp;&nbsp;$ch = curl_init(BASE . $path);\n\n&nbsp;&nbsp;&nbsp;&nbsp;curl_setopt_array($ch, &#91;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CURLOPT_POST =&gt; true,\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CURLOPT_RETURNTRANSFER =&gt; true,\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CURLOPT_HTTPHEADER =&gt; &#91;\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'Authorization: ApiKey ' . getenv('SHORTPIXEL_API_KEY'),\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'Content-Type: application\/json',\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;],\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CURLOPT_POSTFIELDS =&gt; json_encode($body),\n\n&nbsp;&nbsp;&nbsp;&nbsp;]);\n\n&nbsp;&nbsp;&nbsp;&nbsp;$res = json_decode(curl_exec($ch), true) ?? &#91;];\n\n&nbsp;&nbsp;&nbsp;&nbsp;curl_close($ch);\n\n&nbsp;&nbsp;&nbsp;&nbsp;if (isset($res&#91;'error'])) {\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;throw new RuntimeException($res&#91;'error']);\n\n&nbsp;&nbsp;&nbsp;&nbsp;}\n\n&nbsp;&nbsp;&nbsp;&nbsp;return $res;\n\n}\n\n$job = capi('\/add-url.php', &#91;\n\n&nbsp;&nbsp;&nbsp;&nbsp;'url' =&gt; 'https:\/\/example.com\/images\/bench.jpg',\n\n&nbsp;&nbsp;&nbsp;&nbsp;'version' =&gt; 'v_2',\n\n&nbsp;&nbsp;&nbsp;&nbsp;'languages' =&gt; 'en',\n\n&nbsp;&nbsp;&nbsp;&nbsp;'title' =&gt; &#91;'chars' =&gt; 60],\n\n&nbsp;&nbsp;&nbsp;&nbsp;'alt' =&gt; &#91;'chars' =&gt; 120],\n\n]);\n\n$delay = 5;\n\ndo {\n\n&nbsp;&nbsp;&nbsp;&nbsp;sleep($delay);\n\n&nbsp;&nbsp;&nbsp;&nbsp;$delay = min($delay * 2, 60);\n\n&nbsp;&nbsp;&nbsp;&nbsp;$res = capi('\/get-url.php', &#91;'id' =&gt; $job&#91;'id']]);\n\n} while (in_array($res&#91;'status'], &#91;0, 1], true));\n\necho $res&#91;'alt'], PHP_EOL;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>JavaScript (Node 18+)<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const BASE = 'https:\/\/capi-gpt.shortpixel.com';\n\nasync function capi(path, body) {\n\n&nbsp;&nbsp;const r = await fetch(BASE + path, {\n\n&nbsp;&nbsp;&nbsp;&nbsp;method: 'POST',\n\n&nbsp;&nbsp;&nbsp;&nbsp;headers: {\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Authorization: `ApiKey ${process.env.SHORTPIXEL_API_KEY}`,\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'Content-Type': 'application\/json',\n\n&nbsp;&nbsp;&nbsp;&nbsp;},\n\n&nbsp;&nbsp;&nbsp;&nbsp;body: JSON.stringify(body),\n\n&nbsp;&nbsp;});\n\n&nbsp;&nbsp;const data = await r.json();\n\n&nbsp;&nbsp;if (data.error) throw new Error(data.error);\n\n&nbsp;&nbsp;return data;\n\n}\n\nconst job = await capi('\/add-url.php', {\n\n&nbsp;&nbsp;url: 'https:\/\/example.com\/images\/bench.jpg',\n\n&nbsp;&nbsp;version: 'v_2',\n\n&nbsp;&nbsp;languages: 'en',\n\n&nbsp;&nbsp;title: { chars: 60 },\n\n&nbsp;&nbsp;alt: { chars: 120 },\n\n});\n\nlet res, delay = 5000;\n\ndo {\n\n&nbsp;&nbsp;await new Promise((ok) =&gt; setTimeout(ok, delay));\n\n&nbsp;&nbsp;delay = Math.min(delay * 2, 60000);\n\n&nbsp;&nbsp;res = await capi('\/get-url.php', { id: job.id });\n\n} while (res.status === 0 || res.status === 1);\n\nconsole.log(res.title, res.alt);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Python<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import os, time, requests\n\nBASE = \"https:\/\/capi-gpt.shortpixel.com\"\n\nHEADERS = {\"Authorization\": f\"ApiKey {os.environ&#91;'SHORTPIXEL_API_KEY']}\"}\n\ndef capi(path, body):\n\n&nbsp;&nbsp;&nbsp;&nbsp;data = requests.post(BASE + path, json=body, headers=HEADERS, timeout=30).json()\n\n&nbsp;&nbsp;&nbsp;&nbsp;if \"error\" in data:\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;raise RuntimeError(data&#91;\"error\"])\n\n&nbsp;&nbsp;&nbsp;&nbsp;return data\n\njob = capi(\"\/add-url.php\", {\n\n&nbsp;&nbsp;&nbsp;&nbsp;\"url\": \"https:\/\/example.com\/images\/bench.jpg\",\n\n&nbsp;&nbsp;&nbsp;&nbsp;\"version\": \"v_2\",\n\n&nbsp;&nbsp;&nbsp;&nbsp;\"languages\": \"en\",\n\n&nbsp;&nbsp;&nbsp;&nbsp;\"title\": {\"chars\": 60},\n\n&nbsp;&nbsp;&nbsp;&nbsp;\"alt\": {\"chars\": 120},\n\n})\n\ndelay = 5\n\nwhile True:\n\n&nbsp;&nbsp;&nbsp;&nbsp;time.sleep(delay)\n\n&nbsp;&nbsp;&nbsp;&nbsp;delay = min(delay * 2, 60)\n\n&nbsp;&nbsp;&nbsp;&nbsp;res = capi(\"\/get-url.php\", {\"id\": job&#91;\"id\"]})\n\n&nbsp;&nbsp;&nbsp;&nbsp;if res&#91;\"status\"] not in (0, 1):\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break\n\nprint(res&#91;\"title\"], res&#91;\"alt\"])<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">In these examples, a failed job (-1) or a blocked host (-2) raises an error, because the response includes error.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Send the URL of a public image and get back SEO metadata for it: title, alt text, caption, long description and, optionally, an SEO-friendly file name. The API works on any website or platform, not only WordPress. The API is asynchronous and has two calls: Base URL: https:\/\/capi-gpt.shortpixel.com. All requests and responses are JSON (Content-Type: [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","template":"","meta":{"footnotes":""},"doc_category":[37,61,59],"glossaries":[],"doc_tag":[],"class_list":["post-2996","docs","type-docs","status-publish","hentry","doc_category-shortpixel-image-optimizer","doc_category-tools-developer-api","doc_category-wordpress-plugins"],"blocksy_meta":[],"year_month":"2026-10","word_count":2031,"total_views":"6","reactions":{"happy":"0","normal":"0","sad":"0"},"author_info":{"name":"admin","author_nicename":"admin_mdli53m5","author_url":"https:\/\/kb.shortpixel.com\/author\/admin_mdli53m5\/"},"doc_category_info":[{"term_name":"ShortPixel Image Optimizer","term_url":"https:\/\/kb.shortpixel.com\/knowledge-base\/category\/shortpixel-image-optimizer\/"},{"term_name":"Tools &amp; Developer API","term_url":"https:\/\/kb.shortpixel.com\/knowledge-base\/category\/tools-developer-api\/"},{"term_name":"WordPress Plugins","term_url":"https:\/\/kb.shortpixel.com\/knowledge-base\/category\/wordpress-plugins\/"}],"doc_tag_info":[],"knowledge_base_info":[],"knowledge_base_slug":[],"_links":{"self":[{"href":"https:\/\/kb.shortpixel.com\/wp-json\/wp\/v2\/docs\/2996","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/kb.shortpixel.com\/wp-json\/wp\/v2\/docs"}],"about":[{"href":"https:\/\/kb.shortpixel.com\/wp-json\/wp\/v2\/types\/docs"}],"author":[{"embeddable":true,"href":"https:\/\/kb.shortpixel.com\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/kb.shortpixel.com\/wp-json\/wp\/v2\/comments?post=2996"}],"version-history":[{"count":3,"href":"https:\/\/kb.shortpixel.com\/wp-json\/wp\/v2\/docs\/2996\/revisions"}],"predecessor-version":[{"id":2999,"href":"https:\/\/kb.shortpixel.com\/wp-json\/wp\/v2\/docs\/2996\/revisions\/2999"}],"wp:attachment":[{"href":"https:\/\/kb.shortpixel.com\/wp-json\/wp\/v2\/media?parent=2996"}],"wp:term":[{"taxonomy":"doc_category","embeddable":true,"href":"https:\/\/kb.shortpixel.com\/wp-json\/wp\/v2\/doc_category?post=2996"},{"taxonomy":"glossaries","embeddable":true,"href":"https:\/\/kb.shortpixel.com\/wp-json\/wp\/v2\/glossaries?post=2996"},{"taxonomy":"doc_tag","embeddable":true,"href":"https:\/\/kb.shortpixel.com\/wp-json\/wp\/v2\/doc_tag?post=2996"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}