Skip to main content
Lesson 6 of 14 · Generate and Edit Images with Grok

Image Sources: URL and Base64

Lesson 6 of 14 Updated November 3, 2026 Beginner
On this page
  1. Providing Images to the Edit Endpoint
  2. URL Format
  3. Base64 Data URI Format
  4. Third Option: A File from the Files API
  5. Utility Function
  6. Hands-On Practice
  7. Key Takeaways

Updated November 3, 2026. Based on the Image Editing page and the xAI Images API reference: a source image can also be specified using the file_id of a file in the Files API, as an exclusive alternative to url. Sources: Image Editing · Images API.

Providing Images to the Edit Endpoint

The edit endpoint accepts source images in three forms: a public URL, a Base64 Data URI, or the file_id of a file already uploaded to the xAI Files API. Each has its use cases; mastering them lets you build flexible editing pipelines.

URL Format

The URL format is the most convenient option as long as the image is already online: nothing to encode, a tiny payload, and a request that remains readable in logs. The key constraint here is public—the image must be retrievable by xAI servers without a session or token.

payload = {
    "model": "grok-imagine-image",
    "prompt": "Add a gold frame around this image",
    "image": {
        "url": "https://exemple.com/ma-photo.jpg"
    }
}

URL Constraints

  • The URL must be publicly accessible (no links protected by authentication)
  • Supported formats are JPEG, PNG, and WebP
  • The image must be accessible quickly (avoid slow servers)

Using Output URLs from the API

This is what makes multi-pass editing practical: the output of one edit becomes the input for the next, without ever downloading the image to your local machine. One caveat, however—these URLs expire, so the chain must execute in one go. For a workflow spread out over time, download it between each step.

import requests
import os

headers = {
    "Authorization": f"Bearer {os.getenv('XAI_API_KEY')}",
    "Content-Type": "application/json"
}

# Generate an image
gen_response = requests.post(
    "https://api.x.ai/v1/images/generations",
    headers=headers,
    json={
        "model": "grok-imagine-image",
        "prompt": "A medieval castle on a hill"
    }
).json()

generated_url = gen_response["data"][0]["url"]

# Edit the generated image
edit_response = requests.post(
    "https://api.x.ai/v1/images/edits",
    headers=headers,
    json={
        "model": "grok-imagine-image",
        "prompt": "Add a dragon flying over the castle",
        "image": {"url": generated_url}
    }
).json()

Base64 Data URI Format

For images stored locally or generated dynamically, use the Base64 Data URI format:

import base64

# Read a local image
with open("photo_locale.jpg", "rb") as f:
    image_bytes = f.read()

b64_string = base64.b64encode(image_bytes).decode("utf-8")
data_uri = f"data:image/jpeg;base64,{b64_string}"

payload = {
    "model": "grok-imagine-image",
    "prompt": "Transform this photo into a vector illustration",
    "image": {
        "url": data_uri
    }
}

When to Use Base64?

The criterion is simple: Base64 is necessary whenever using a public URL would be an unnecessary detour. This applies to local images that are not hosted anywhere—uploading them to a server just to make them accessible to the API adds a step and delay with no value. It also applies to images generated in memory by your application: a chart produced on the fly, a screenshot of a render, or a dynamically composed thumbnail never need to touch the disk. Base64 also addresses a privacy concern—an image encoded in the request body is never exposed on a public server, even temporarily—and simplifies architecture: an automated pipeline chaining generation, editing, and saving works end-to-end without intermediate hosting to provision, secure, and clean up.

Watch the Size

Base64 bloats the payload by about a third, which is negligible for a thumbnail but becomes significant for a multi-megabyte photo—longer upload times and a risk of exceeding request size limits. The rule of thumb: if the image is already accessible online, use the URL; if it is local, Base64 is simpler than hosting it just for the occasion.

Third Option: A File from the Files API

An image already uploaded to the xAI Files API is specified by its identifier, instead of a URL. The two fields are mutually exclusive: an image object carries either url or file_id.

payload = {
    "model": "grok-imagine-image",
    "prompt": "Add snow to the landscape",
    "image": {"file_id": "file_abc123"}
}

This is the natural choice for an image reused across multiple edits: it is sent only once, and it does not have to be public.

Utility Function

Here is a function that handles both formats automatically:

import base64
import os

def prepare_image_source(source):
    """Prepares an image source for the API (URL or local file)."""
    if source.startswith("http://") or source.startswith("https://"):
        return {"url": source}

    if os.path.isfile(source):
        ext = source.rsplit(".", 1)[-1].lower()
        mime_map = {"jpg": "jpeg", "jpeg": "jpeg", "png": "png", "webp": "webp"}
        mime_type = mime_map.get(ext, "jpeg")

        with open(source, "rb") as f:
            b64 = base64.b64encode(f.read()).decode("utf-8")
        return {"url": f"data:image/{mime_type};base64,{b64}"}

    raise ValueError(f"Invalid source: {source}")

This function automatically detects whether the source is a URL or a local file path and formats the object accordingly.

Hands-On Practice

Test both formats with the same image and prompt to verify that both paths work; the resulting images will vary from run to run, like any generation:

# Via URL
payload_url = {
    "model": "grok-imagine-image",
    "prompt": "Add snow to the landscape",
    "image": {"url": "https://exemple.com/paysage.jpg"}
}

# Via Base64
payload_b64 = {
    "model": "grok-imagine-image",
    "prompt": "Add snow to the landscape",
    "image": prepare_image_source("paysage.jpg")
}

Key Takeaways

  • Three input forms: public URL, Base64 Data URI, or file_id from the Files API
  • Temporary URLs returned by the API can serve as editing sources
  • Base64 increases payload size by ~33%
  • Create a utility function to handle both formats automatically
  • The URL must be publicly accessible to work