An accepted image-generation request is not a finished image. Your application needs to keep the task ID, check the same task, and handle a failed or interrupted request without accidentally starting another job.

This tutorial builds a small server-side Node.js client for that sequence. It has separate submit and resume commands: one creates a task, while the other only checks an existing task. Start with the supported API model IDs, not a model name copied from a homepage banner.

The tutorial covers text-to-image generation. It does not upload reference files, implement a browser interface, or measure model quality. Validation combines 29 local mock tests with one controlled production image task on September 4, 2026. That task returned one image and recorded a 120-point API charge. This is one dated integration check, not a speed benchmark, a universal price, or a guarantee of availability for another account.

The cover is a model-catalog screenshot captured on September 4, 2026. Treat it as a dated illustration, not a current price quote or guarantee of availability.

Key Takeaways

  • Keep the API key on your server, outside browser bundles and public repositories.
  • For an image task, read the numeric data.taskId from the accepted response and keep it for later queries.
  • Poll the saved task until ready or failed. Read image URLs from data.images only after success.
  • A client timeout does not establish that the remote task failed or was canceled. Resume queries when you have a task ID; do not blindly repeat the creation request.

In this guide

  • Before you start
  • Choose a model and configure your API key
  • Submit with cURL
  • Poll and read image URLs
  • Complete Node.js example
  • Handle errors
  • Video and chat differences
  • Verify your integration

Before you start

Use a private terminal or server environment with Node.js, an API key, and an account configured for API billing. The accompanying example uses built-in fetch and JavaScript modules, with no third-party package installation. Node's global API documentation describes fetch and AbortSignal.timeout, which the client uses to limit an individual request. Its local validation environment is Windows with Node.js v22.22.1; that is a test-environment record, not a claim that every other version has been tested.

You should be comfortable editing a file, setting an environment variable, and reading JSON. Check your installed runtime before continuing:

node --version 

The command should print your installed version. It does not contact the generation API. Save the complete client from the later section as examples/pixmind-image.mjs inside a working folder, then run its commands from that folder.

Choose a harmless test prompt without customer data or confidential material. This tutorial uses a ceramic coffee cup on a studio background, so it needs no reference upload. Review the selected model's current API price before submitting. Studio credits, subscriptions, and API billing should not be assumed to be interchangeable; the model catalog explains the applicable API offering.

If you only want to make an image interactively, the Image Agent guide describes that workflow. The client here is for an application that explicitly supplies a model ID and handles the response itself.

Choose a model and configure your API key

Use a model ID supported by the API and parameters supported by that model. The example selects nano-banana-pro, with aspectRatio: "1:1" and resolution: "1K", matching the image request in the API Quickstart. An authenticated GET /models and the production test confirmed that combination for the test account on September 4, 2026. Recheck availability and parameter support for your own account before a live run. Changing only the model string is not enough to guarantee that the rest of a request remains valid.

Create a key in the API console, then provide it to the server process as PIXMIND_API_KEY. Use your private secret-management mechanism where available. The following examples show environment-variable syntax with a placeholder, not a working credential.

For PowerShell:

$env:PIXMIND_API_KEY = "REPLACE_WITH_YOUR_PRIVATE_API_KEY" $env:PIXMIND_MODEL = "nano-banana-pro" $env:PIXMIND_PROMPT = "A ceramic coffee cup on a plain studio background, soft side lighting, no text" 

For Bash:

export PIXMIND_API_KEY="REPLACE_WITH_YOUR_PRIVATE_API_KEY" export PIXMIND_MODEL="nano-banana-pro" export PIXMIND_PROMPT="A ceramic coffee cup on a plain studio background, soft side lighting, no text" 

These assignments configure the current shell and processes launched from it. They do not create a task. Protect shell history and recordings when entering a real secret, and never commit that secret to source control. Follow the authentication guidance for separate development and production keys and rotation after exposure.

The base URL for this tutorial is:

https://aihub-admin.aimix.pro/api-platform/v1 

Append /generations or /tasks/{taskId} to that base. Do not append a second /v1. Model discovery is available through GET /models; the catalog is the more convenient place to read model-specific capabilities before choosing parameters.

Submit your first image request with cURL

The cURL request is an alternative to the Node.js submit command, not a setup step you must run first. Both submit a generation request. Running both can create two billable tasks. If you submit with cURL, use the Node.js resume command afterward with the returned task ID.

This example uses Bash syntax. In PowerShell, use the Node.js client below instead of pasting Bash line continuations into the terminal.

curl --connect-timeout 10 --max-time 30 \ --request POST \ 'https://aihub-admin.aimix.pro/api-platform/v1/generations' \ --header "Authorization: Bearer $PIXMIND_API_KEY" \ --header 'Content-Type: application/json' \ --data '{ "model": "nano-banana-pro", "type": "image", "prompt": "A ceramic coffee cup on a plain studio background, soft side lighting, no text", "aspectRatio": "1:1", "resolution": "1K" }' 

The prompt and parameters in this cURL body are literal values. Changing PIXMIND_PROMPT does not change this JSON; that environment variable is read by the Node.js client. This distinction helps keep the request you intend to send separate from the shell configuration around it.

An abbreviated illustrative accepted response is shown below. Its ID is a placeholder, not the production test's ID; 12345 is not a task you should query.

{ "code": 1000, "message": "success", "data": { "id": "img_12345", "taskId": 12345 } } 

The tested production media responses wrapped successful data in an object with code, message, data, and timestamp; the illustration omits the timestamp and other fields. For this workflow, success requires both a successful HTTP response and a valid application response. Read data.taskId, not data.id: the latter may contain a prefixed string such as img_12345, while the task-query path uses the numeric ID.

Save that numeric ID immediately. If the request times out before you receive it, stop and reconcile the submission with your account's task records or support. Repeating the POST is not a safe substitute for finding out what happened.

Poll the task and read the image URLs

Query the existing task until it reaches a terminal state. The Async Tasks documentation describes pending, processing, ready, and failed. Only the latter two end this polling workflow.

Submit once | Save numeric taskId | GET /tasks/{taskId} <--- wait, then query again | ^ +--- pending / processing ---+ | +--- ready ---> read images, stop | +--- failed ---> report failure, stop 

For a manual query in Bash, replace the placeholder ID with your own:

TASK_ID="REPLACE_WITH_YOUR_NUMERIC_TASK_ID" curl --connect-timeout 10 --max-time 30 \ "https://aihub-admin.aimix.pro/api-platform/v1/tasks/$TASK_ID" \ --header "Authorization: Bearer $PIXMIND_API_KEY" 

In the current media response contract, the fields you need have different jobs:

Field Meaning for this client
data.taskId Numeric identifier used to query the existing task
data.status Whether to keep waiting, read the result, or stop on failure
data.images Array of image output URLs, used after ready
data.videoUrl Video output field, not the image result array

A ready response with no usable image URLs is not a successful image result for your application. Surface that mismatch with the task ID so it can be investigated. Do not substitute an example URL or report that an image was downloaded.

The client prints returned image URLs; it does not fetch or archive the image files. If your application needs durable storage, design that as a separate step and confirm the relevant asset-retention and usage terms. Do not infer a permanent storage guarantee from the presence of a URL.

Run the complete Node.js example

Use submit for one new task, or resume for an existing task. Running the script without arguments prints usage and makes no API call. That default prevents an ordinary restart or a quick inspection of the CLI from creating a fresh job.

Save the full source below as examples/pixmind-image.mjs:

Complete Node.js source: pixmind-image.mjs

import { pathToFileURL } from 'node:url'; const BASE_URL = 'https://aihub-admin.aimix.pro/api-platform/v1'; const sleep = ms =&gt; new Promise(resolve =&gt; setTimeout(resolve, ms)); export class ApiError extends Error { constructor(message, { status = 0, transient = false, retryAfterMs = 0 } = {}) { super(message); Object.assign(this, { status, transient, retryAfterMs }); } } export function parseTaskId(value) { if (!/^\d+$/.test(String(value))) throw new Error('Use a numeric taskId, not img_...'); const id = Number(value); if (!Number.isSafeInteger(id) || id &lt;= 0) throw new Error('Invalid taskId'); return id; } export function retryAfter(value, now) { if (!value) return 0; if (/^\d+(\.\d+)?$/.test(value)) return Number(value) * 1000; const date = Date.parse(value); return Number.isFinite(date) ? Math.max(0, date - now) : 0; } export function createClient({ apiKey, fetchImpl = fetch, now = Date.now, wait = sleep, random = Math.random, requestTimeoutMs = 30_000, pollTimeoutMs = 300_000, maxConsecutiveErrors = 5, } = {}) { if (typeof apiKey !== 'string' || !apiKey.trim()) throw new Error('Set PIXMIND_API_KEY'); async function request(path, method, payload, timeoutMs = requestTimeoutMs) { let response; let body; try { response = await fetchImpl(`${BASE_URL}${path}`, { method, redirect: 'error', headers: { Authorization: `Bearer ${apiKey}`, ...(payload ? { 'Content-Type': 'application/json' } : {}), }, ...(payload ? { body: JSON.stringify(payload) } : {}), signal: AbortSignal.timeout(Math.max(1, Math.ceil(timeoutMs))), }); // Read the body inside the timeout/network error boundary as well. body = await response.text(); } catch { throw new ApiError('Network error or request timeout', { transient: true }); } let envelope; try { envelope = JSON.parse(body); } catch { /* handle below */ } const transient = response.status === 429 || response.status &gt;= 500; if (!response.ok) { // Do not echo arbitrary server bodies, prompts, credentials, or output URLs. throw new ApiError(`HTTP ${response.status}; inspect the account and request`, { status: response.status, transient, retryAfterMs: retryAfter(response.headers.get('retry-after'), now()), }); } if (!envelope || typeof envelope !== 'object') { throw new ApiError('Expected a JSON API response'); } if (envelope.code !== 1000 || !envelope.data) { throw new ApiError('API returned an unsuccessful or incomplete envelope'); } return envelope.data; } async function submit({ model = 'nano-banana-pro', prompt } = {}) { if (typeof model !== 'string' || !model.trim()) throw new Error('A model is required'); if (typeof prompt !== 'string' || !prompt.trim()) throw new Error('A prompt is required'); try { const data = await request('/generations', 'POST', { model, type: 'image', prompt, aspectRatio: '1:1', resolution: '1K', }); // The public media contract returns a number, not the prefixed display ID. if (typeof data.taskId !== 'number') throw new Error('Missing numeric taskId'); return parseTaskId(data.taskId); } catch (error) { // A timeout or malformed response does not prove that creation failed. throw new Error(`Submission not confirmed: ${error.message}. No automatic retry was made. Check task records before submitting again.`); } } async function poll(taskId) { const id = parseTaskId(taskId); const deadline = now() + pollTimeoutMs; let attempts = 0; let errors = 0; const timedOut = () =&gt; new Error(`Stopped waiting for task ${id}; it may still be running. Resume this ID later.`); while (now() &lt; deadline) { let retryFloor = 0; let task; try { task = await request(`/tasks/${id}`, 'GET', undefined, Math.min(requestTimeoutMs, deadline - now())); errors = 0; } catch (error) { if (now() &gt;= deadline) throw timedOut(); if (!(error instanceof ApiError) || !error.transient) throw error; errors += 1; if (errors &gt;= maxConsecutiveErrors) { throw new Error(`Stopped after ${errors} consecutive query errors for task ${id}; resume this ID later.`); } retryFloor = error.retryAfterMs; } if (now() &gt;= deadline) throw timedOut(); if (task) { if (task.taskId !== id) throw new Error('Task response ID does not match the requested task'); if (task.status === 'failed') throw new Error(`Task ${id} failed; inspect its record before creating another task.`); if (task.status === 'ready') { if (!Array.isArray(task.images) || task.images.length === 0 || !task.images.every(url =&gt; { try { return ['https:', 'http:'].includes(new URL(url).protocol); } catch { return false; } })) throw new Error(`Task ${id} is ready but has no valid image URLs`); return task.images; } if (!['pending', 'processing'].includes(task.status)) { throw new Error(`Task ${id} returned an unrecognized status; inspect its record.`); } } // Client policy, not a PixMind latency guarantee or server-side retry feature. const ceiling = Math.min(10_000, 1_000 * 2 ** Math.min(attempts++, 4)); const delay = Math.max(retryFloor, ceiling * (0.5 + 0.5 * random())); const remaining = deadline - now(); if (delay &gt;= remaining) { // Never poll earlier than Retry-After just to fit the local deadline. await wait(Math.max(0, remaining)); throw timedOut(); } await wait(delay); } throw timedOut(); } return { submit, poll }; } export async function main(args = process.argv.slice(2), env = process.env, deps = {}) { const log = deps.log ?? console.log; const [mode, rawId] = args; if (!mode) { log('Usage: node examples/pixmind-image.mjs submit | resume TASK_ID'); return; } if (!((mode === 'submit' &amp;&amp; args.length === 1) || (mode === 'resume' &amp;&amp; args.length === 2))) { throw new Error('Use submit, or resume followed by a numeric taskId'); } const resumeId = mode === 'resume' ? parseTaskId(rawId) : undefined; const client = createClient({ ...depsSource:https://dev.to/pixmind/pixmind-api-quickstart-generate-images-with-nodejs-5af1

Related posts