Last reviewed: 2026-08-01

Direct answer

Use a server-side Node.js process to send a multipart POST request to CometAPI’s /v1/images/edits path. Add the model, edit prompt, source image, and optional mask to FormData; then validate both the HTTP result and the returned image field before writing any bytes to disk.

The current CometAPI GPT-image-1 API guide identifies gpt-image-1, the edit path, and its OpenAI-compatible Images format. The OpenAI image generation guide distinguishes the Image API’s Edits operation from Generations and recommends the Image API for a single edit driven by one prompt. CometAPI’s image-editing walkthrough describes the source-image, prompt, and optional-mask workflow.

Keep authentication inside an existing server-only HTTP wrapper. The sample below intentionally imports that wrapper instead of embedding or printing its configuration. It also keeps the endpoint in application configuration, so no service URL is hidden in the code.

import { basename } from 'node:path';
import { readFile, writeFile } from 'node:fs/promises';
import { authenticatedFetch, editEndpoint } from './cometapi-client.js';

const MODEL = 'gpt-image-1';

function editError(code, status = null) {
  const error = new Error(code);
  error.code = code;
  error.status = status;
  return error;
}

export async function editImage({
  inputPath,
  maskPath = null,
  outputPath,
  prompt
}) {
  if (!inputPath || !outputPath) {
    throw editError('missing_file_path');
  }

  if (!prompt || !prompt.trim()) {
    throw editError('missing_prompt');
  }

  if (typeof FormData !== 'function' || typeof Blob !== 'function') {
    throw editError('unsupported_node_runtime');
  }

  const imageBytes = await readFile(inputPath);
  const maskBytes = maskPath ? await readFile(maskPath) : null;

  const form = new FormData();
  form.append('model', MODEL);
  form.append('prompt', prompt.trim());
  form.append('size', '1024x1024');
  form.append(
    'image',
    new Blob([imageBytes], { type: 'image/png' }),
    basename(inputPath)
  );

  if (maskBytes) {
    form.append(
      'mask',
      new Blob([maskBytes], { type: 'image/png' }),
      basename(maskPath)
    );
  }

  const startedAt = Date.now();
  const response = await authenticatedFetch(editEndpoint, {
    method: 'POST',
    body: form,
    signal: AbortSignal.timeout(90_000)
  });

  const payload = await response.json().catch(() => null);

  if (!response.ok) {
    throw editError('upstream_rejected', response.status);
  }

  const encodedImage = payload?.data?.[0]?.b64_json;
  if (typeof encodedImage !== 'string' || encodedImage.length === 0) {
    throw editError('missing_image_data', response.status);
  }

  const outputBytes = Buffer.from(encodedImage, 'base64');
  if (outputBytes.length === 0) {
    throw editError('empty_decoded_image', response.status);
  }

  await writeFile(outputPath, outputBytes);

  return {
    status: response.status,
    durationMs: Date.now() - startedAt,
    inputBytes: imageBytes.length,
    maskBytes: maskBytes?.length ?? 0,
    outputBytes: outputBytes.length,
    itemCount: Array.isArray(payload?.data) ? payload.data.length : 0,
    upstreamRequestIdPresent: Boolean(response.headers.get('x-request-id'))
  };
}

This example uses the globals documented in the current Node.js global objects reference , including Blob, FormData, and AbortSignal. Do not manually replace the multipart content type with application/json; the form submission needs its multipart boundary. The MDN FormData.append reference confirms that a field can contain a string or Blob and that a filename can accompany a Blob upload.

A concrete operator workflow has five stages:

  1. Preflight the run. Confirm that the source file is readable, the optional mask is the intended PNG, the prompt is nonempty, the output path is correct, and the server-only client is configured.
  2. Record sanitized input metadata before the request: model, filenames without directory paths, byte counts, whether a mask is present, and a local operation identifier. Do not record file contents, the prompt, authentication material, or full request headers.
  3. Send exactly one edit request and measure its duration. On the happy path, require a successful status, a nonempty data array, a nonempty encoded image value, successful decoding, and a successful file write.
  4. Open the saved result and compare it with the source, mask, and prompt. A technically valid file is not proof that the requested region or object changed correctly.
  5. On the error path, do not create a success record or pretend a missing output field is an empty image. Classify the failure as local validation, timeout, upstream rejection, response-contract mismatch, decoding failure, or file-write failure. Preserve only sanitized diagnostics.

A useful log allowlist looks like this; the values are illustrative and contain no source content:

const logEvent = {
  event: 'cometapi.image_edit',
  outcome: 'success',
  http_status: 200,
  duration_ms: 1840,
  model: 'gpt-image-1',
  input_filename: 'source.png',
  input_bytes: 245120,
  mask_present: true,
  mask_bytes: 38120,
  output_bytes: 612840,
  response_item_count: 1,
  upstream_request_id_present: true,
  error_code: null
};

For a failure, change outcome to error, set error_code to the local classification, retain the real HTTP status when one exists, and leave output fields at zero or null. Do not add the raw response body to routine logs because it may contain more detail than an operator needs.

Who this is for

This tutorial is for Node.js developers building a server-side image-editing feature: a product-image adjustment tool, a controlled design workflow, an internal creative utility, or a backend route that accepts an image and edit instruction. It assumes you can supply an authenticated server-side request wrapper and have permission to process the input image.

It is not a browser-only upload recipe. Sending service authentication from frontend code would put the trust boundary in the wrong place. If that boundary is new to your project, first read how to keep CometAPI keys out of tutorial repositories . You should also verify the CometAPI base URL before publishing and review request headers before shipping .

Key takeaways

  • Use the Images edit operation for a single prompt-driven edit of an existing image; do not substitute the generation path.
  • Send multipart form data, not a JSON body, because the request includes binary image content.
  • Treat the mask as optional. When used, prepare it deliberately and verify its dimensions and transparency against the current model contract.
  • Keep authentication and endpoint configuration in a server-only client boundary.
  • Validate the response shape before decoding or writing a file. A successful HTTP status alone is insufficient.
  • Log status, duration, model, byte counts, mask presence, response item count, and a controlled error code. Exclude prompts, image bytes, response bodies, and authentication data.
  • Inspect the actual visual result. Transport success and semantic edit quality are separate checks.

Sources checked

These sources were reachable when this article was reviewed. The two CometAPI pages establish the gateway-specific starting point; the upstream and runtime references clarify the compatible edit operation and Node.js upload mechanics.

Contract details to verify

Before moving from a controlled test to a user-facing feature, verify each of these details against the current CometAPI model documentation and an observed sanitized response:

  1. Endpoint family: Confirm that your configured client resolves to the Images edit path, /v1/images/edits, rather than the generation path. A base URL plus the wrong path can still produce a well-formed but irrelevant error.
  2. Model availability: Confirm that gpt-image-1 is currently available to the account and supports editing through that path. Do not infer availability from a model name alone.
  3. Multipart field names: Confirm model, prompt, image, and optional mask. Field names are contract data; renaming image to an application-specific label changes what the upstream service receives.
  4. File preparation: The refetched CometAPI editing walkthrough describes square inputs and a transparent PNG mask whose transparent region marks the area to edit. Verify the current accepted formats, dimensions, byte limits, and mask semantics before accepting arbitrary uploads.
  5. Form behavior: FormData.append can retain multiple values under one name, while set replaces existing values. Use one source image for this specific workflow unless the current endpoint documentation explicitly calls for repeated image fields.
  6. Output shape: The example checks the OpenAI-compatible data[0].b64_json shape. Capture only field names and types from a sanitized test response, then update the parser if CometAPI documents a different output form. Never treat an undocumented fallback as guaranteed.
  7. Output format: Match the saved extension and downstream validation to the actual returned media format. Base64 decoding proves that bytes exist; it does not establish that the bytes match the filename extension.
  8. Timeout and retry policy: Set a bounded timeout. Before adding automatic retries, verify provider guidance, billing behavior, and whether a timed-out request may still complete upstream.
  9. Error schema: Branch first on HTTP success versus failure, then inspect documented fields. Avoid parsing one remembered error message as a permanent contract.
  10. Observability boundary: Decide in advance which filenames, sizes, status values, and identifiers are safe to retain. Keep prompts and image contents out of ordinary operational logs unless a separate, explicit data policy permits them.

This verification step matters because compatibility describes an interface family, not a promise that every model supports every historical parameter. A narrow request and a strict parser are easier to maintain than a payload containing speculative options.

Failure modes

  • The request reaches the wrong path. A generation endpoint cannot perform the source-image edit described here. Log the status and local classification, then recheck the configured endpoint family.
  • The multipart boundary is broken. Replacing the form-generated content type with a JSON content type can make otherwise correct fields unreadable. Remove the manual override and inspect only sanitized request metadata.
  • The source file is missing or unreadable. readFile fails before any upstream request. Classify this as local validation or filesystem failure and avoid a network retry.
  • The mask does not align with the source. Different dimensions, an unexpected alpha channel, or inverted transparent regions can produce rejection or an edit in the wrong area. Inspect the mask visually and verify the current mask rules.
  • Authentication is rejected. Stop and correct the server-side client configuration. Do not print its configuration, copy it into a query string, or retry repeatedly.
  • The model or parameter combination is unsupported. Preserve the HTTP status and a short local error code. Recheck the model’s current edit contract instead of cycling through guessed parameters.
  • The request times out or the connection drops. Record elapsed time and that no complete response was received. Do not assume the upstream operation was canceled merely because the local wait ended.
  • The response is not JSON. The sample converts that case into a controlled failure. Store the status and content type if safe, but not the entire body.
  • A successful status lacks image data. Treat this as a response-contract mismatch. Do not write an empty file or silently return success.
  • Base64 decoding yields no bytes. Stop before the file write and classify the response as invalid image data.
  • The file cannot be written. Permissions, an invalid destination, or unavailable storage can fail after a valid edit response. Report this separately from the upstream request so operators know which system failed.
  • The image is valid but the edit is wrong. Tighten the prompt, simplify the requested change, and verify the mask. Do not confuse visual acceptance with transport validation.

A good error path leaves three things: no false success signal, no misleading output file, and one sanitized event that identifies the failing stage.

FAQ

Is a mask required?

No. The refetched CometAPI editing workflow describes the mask as optional. Without one, the prompt guides the edit more broadly. With one, the mask is intended to constrain the target area, subject to the current model’s documented behavior.

Why does the sample use PNG uploads?

The concrete workflow follows the CometAPI editing guidance around a transparent PNG mask and uses a single predictable media type for the source example. Verify current input formats before accepting JPEG, WebP, or other files, and validate actual content rather than trusting an extension.

Should I set the multipart content type myself?

No. Pass the FormData object as the request body and let the request implementation supply the multipart boundary. Your authenticated wrapper should add only its required service configuration without replacing the form’s content type.

Why check both the status and data[0].b64_json?

They answer different questions. The status indicates whether the HTTP operation succeeded; the field check confirms that the payload contains the image form the parser expects. Requiring both prevents an empty or changed response from becoming a false success.

Can I use conversational image editing instead?

The upstream OpenAI guide describes the Responses API as the option for multi-turn image workflows, while the Image API suits a single edit. This tutorial covers CometAPI’s documented Images edit path. Verify CometAPI support and its exact contract before adopting a different endpoint family.

Should failed requests be retried automatically?

Do not retry missing files, invalid masks, rejected authentication, unsupported parameters, or response-contract mismatches. For apparently transient network or server failures, add retries only after confirming current provider guidance and the consequences of duplicate work. Use bounded attempts and preserve the first failure classification.

What should an operator retain?

Retain a local operation identifier, outcome, stage, HTTP status when available, duration, model, basename-only filenames, byte counts, mask presence, response item count, output byte count, and whether an upstream request identifier was present. Exclude prompt text, file bytes, raw bodies, and authentication material from routine logs.

Reader next step

Run one controlled edit with a non-sensitive square PNG, a simple prompt, and an output path reserved for the test. First run it without a mask; then add a transparent PNG mask and compare the two outputs. Confirm the endpoint path, returned field names, media format, sanitized log event, and error behavior before connecting the function to user uploads.

When that local contract check is ready, Start with CometAPI and keep the service boundary on the server.