Imagen API docs
Navigation menu
Reference

Smart Editing (I2I)

Smart Editing detects the best editing style for your photos. It uses Imagen’s image-to-image model. No AI Profile is required. The endpoint surface lives under /v1/i2i/projects/... and parallels the profile-based flow, but the edit body is much smaller and the output JPEGs are ready as soon as the project reports Completed. There is no separate export step.

Shooting real estate?

This page is the endpoint reference. For when to use Smart Editing versus the profile-based flow, the built-in presets, and genre-specific guidance, see the Real estate guide.

Flow overview

  1. POST /v1/i2i/projects - create the project.
  2. POST /v1/i2i/projects/{uuid}/get_temporary_upload_links - presigned S3 PUTs.
  3. PUT <upload_link> with an empty Content-Type - upload image bytes.
  4. POST /v1/i2i/projects/{uuid}/edit - run the smart edit.
  5. GET /v1/i2i/projects/{uuid} - poll until status is Completed.
  6. GET /v1/i2i/projects/{uuid}/get_temporary_download_links - download JPEGs.

1. Create the project

POST /v1/i2i/projects

Names must be unique per account. If you omit name or send an empty body, the server auto-generates one for you.

curl -X POST 'https://api.imagen-ai.com/v1/i2i/projects' \
  --header 'x-api-key: $IMAGEN_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{"name": "Real Estate Listing 1734567890"}'
project_uuid = await client.create_i2i_project("Real Estate Listing 1734567890")
const projectUuid = await client.createI2iProject('Real Estate Listing 1734567890');
projectUUID, _ := client.CreateI2IProject(ctx, "Real Estate Listing 1734567890")
import java.net.URI;
import java.net.http.*;

var body = """
    {"name": "Real Estate Listing 1734567890"}
    """;
var request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.imagen-ai.com/v1/i2i/projects"))
    .header("x-api-key", System.getenv("IMAGEN_API_KEY"))
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString(body))
    .build();
var response = HttpClient.newHttpClient()
    .send(request, HttpResponse.BodyHandlers.ofString());
require 'net/http'
require 'json'
require 'uri'

uri = URI("https://api.imagen-ai.com/v1/i2i/projects")
req = Net::HTTP::Post.new(uri)
req['x-api-key'] = ENV['IMAGEN_API_KEY']
req['Content-Type'] = 'application/json'
req.body = { name: 'Real Estate Listing 1734567890' }.to_json

res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
project_uuid = JSON.parse(res.body)['data']['project_uuid']
<?php
$ch = curl_init('https://api.imagen-ai.com/v1/i2i/projects');
curl_setopt_array($ch, [
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_POST => true,
  CURLOPT_HTTPHEADER => [
    'x-api-key: ' . getenv('IMAGEN_API_KEY'),
    'Content-Type: application/json',
  ],
  CURLOPT_POSTFIELDS => json_encode(['name' => 'Real Estate Listing 1734567890']),
]);
$response = curl_exec($ch);
curl_close($ch);
$project_uuid = json_decode($response, true)['data']['project_uuid'];
using System.Net.Http;
using System.Text;
using System.Text.Json;

using var client = new HttpClient();
client.DefaultRequestHeaders.Add("x-api-key",
    Environment.GetEnvironmentVariable("IMAGEN_API_KEY"));

var payload = JsonSerializer.Serialize(new { name = "Real Estate Listing 1734567890" });
var content = new StringContent(payload, Encoding.UTF8, "application/json");
var response = await client.PostAsync(
    "https://api.imagen-ai.com/v1/i2i/projects", content);
var json = await response.Content.ReadAsStringAsync();
{
  "data": {
    "project_uuid": "65f3a1c2d4e5f6a7b8c9d0e1"
  }
}

Save the project_uuid. Every subsequent call uses it.

Check a name before creating

GET /v1/i2i/projects/is_valid_name?name=NAME
curl 'https://api.imagen-ai.com/v1/i2i/projects/is_valid_name?name=$NAME' \
  --header 'x-api-key: $IMAGEN_API_KEY'
is_valid = await client.validate_i2i_project_name(name)
const isValid = await client.validateI2iProjectName(name);
isValid, _ := client.IsValidI2IName(ctx, name)
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.*;
import java.nio.charset.StandardCharsets;

var uri = URI.create("https://api.imagen-ai.com/v1/i2i/projects/is_valid_name?name="
    + URLEncoder.encode(name, StandardCharsets.UTF_8));
var request = HttpRequest.newBuilder()
    .uri(uri)
    .header("x-api-key", System.getenv("IMAGEN_API_KEY"))
    .GET()
    .build();
var response = HttpClient.newHttpClient()
    .send(request, HttpResponse.BodyHandlers.ofString());
require 'net/http'
require 'json'
require 'uri'

uri = URI("https://api.imagen-ai.com/v1/i2i/projects/is_valid_name")
uri.query = URI.encode_www_form(name: name)
req = Net::HTTP::Get.new(uri)
req['x-api-key'] = ENV['IMAGEN_API_KEY']

res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
is_valid = JSON.parse(res.body)['data']['is_valid']
<?php
$url = 'https://api.imagen-ai.com/v1/i2i/projects/is_valid_name?'
  . http_build_query(['name' => $name]);
$ch = curl_init($url);
curl_setopt_array($ch, [
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ['x-api-key: ' . getenv('IMAGEN_API_KEY')],
]);
$response = curl_exec($ch);
curl_close($ch);
$is_valid = json_decode($response, true)['data']['is_valid'];
using System.Net.Http;
using System.Text.Json;

using var client = new HttpClient();
client.DefaultRequestHeaders.Add("x-api-key",
    Environment.GetEnvironmentVariable("IMAGEN_API_KEY"));

var response = await client.GetAsync(
    $"https://api.imagen-ai.com/v1/i2i/projects/is_valid_name?name={Uri.EscapeDataString(name)}");
var json = await response.Content.ReadAsStringAsync();
{
  "data": {
    "is_valid": true
  }
}
POST /v1/i2i/projects/PROJECT_UUID/get_temporary_upload_links

The request and response shape is the same as the profile-based flow: one file_name per file, and filenames must be unique. See Uploading for the full request body, the unique-filenames rule, and multipart upload for large files.

3. Upload the files

PUT each file to its upload_link with an explicit empty Content-Type header and no x-api-key. The mechanics are identical to the Quickstart flow and are documented once on the Uploading page.

4. Trigger Smart Editing

The edit body is much smaller than the profile-based flow: no profile_key, no photography_type, no export.

POST /v1/i2i/projects/PROJECT_UUID/edit
curl -X POST 'https://api.imagen-ai.com/v1/i2i/projects/$PROJECT_UUID/edit' \
  --header 'x-api-key: $IMAGEN_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
    "hdr_merge": true,
    "perspective_correction": true,
    "sky_replacement": true
  }'
from imagen_sdk import I2IEditOptions

result = await client.start_i2i_editing(
    project_uuid,
    I2IEditOptions(
        hdr_merge=True,
        perspective_correction=True,
        sky_replacement=True,
    ),
)
print(result.message)
const result = await client.startI2iEditing(projectUuid, {
  hdr_merge: true,
  perspective_correction: true,
  sky_replacement: true,
});
console.log(result.message);
import imagen "github.com/imagenai/imagen-ai-sdk/sdks/go"

opts := imagen.I2IEditOptions{
    HDRMerge:              imagen.Bool(true),
    PerspectiveCorrection: imagen.Bool(true),
    SkyReplacement:        imagen.Bool(true),
}
if err := client.StartI2IEditing(ctx, projectUUID, &opts); err != nil {
    log.Fatal(err)
}
import java.net.URI;
import java.net.http.*;

var body = """
    {
        "hdr_merge": true,
        "perspective_correction": true,
        "sky_replacement": true
    }
    """;
var request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.imagen-ai.com/v1/i2i/projects/" + projectUuid + "/edit"))
    .header("x-api-key", System.getenv("IMAGEN_API_KEY"))
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString(body))
    .build();
var response = HttpClient.newHttpClient()
    .send(request, HttpResponse.BodyHandlers.ofString());
require 'net/http'
require 'json'
require 'uri'

uri = URI("https://api.imagen-ai.com/v1/i2i/projects/#{project_uuid}/edit")
req = Net::HTTP::Post.new(uri)
req['x-api-key'] = ENV['IMAGEN_API_KEY']
req['Content-Type'] = 'application/json'
req.body = {
  hdr_merge: true,
  perspective_correction: true,
  sky_replacement: true,
}.to_json

res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
<?php
$ch = curl_init('https://api.imagen-ai.com/v1/i2i/projects/' . $projectUuid . '/edit');
curl_setopt_array($ch, [
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_POST => true,
  CURLOPT_HTTPHEADER => [
    'x-api-key: ' . getenv('IMAGEN_API_KEY'),
    'Content-Type: application/json',
  ],
  CURLOPT_POSTFIELDS => json_encode([
    'hdr_merge' => true,
    'perspective_correction' => true,
    'sky_replacement' => true,
  ]),
]);
$response = curl_exec($ch);
curl_close($ch);
using System.Net.Http;
using System.Text;
using System.Text.Json;

using var client = new HttpClient();
client.DefaultRequestHeaders.Add("x-api-key",
    Environment.GetEnvironmentVariable("IMAGEN_API_KEY"));

var payload = JsonSerializer.Serialize(new {
    hdr_merge = true,
    perspective_correction = true,
    sky_replacement = true,
});
var content = new StringContent(payload, Encoding.UTF8, "application/json");
var response = await client.PostAsync(
    $"https://api.imagen-ai.com/v1/i2i/projects/{projectUuid}/edit", content);
{
  "data": {
    "message": "Project ... sent for I2I editing successfully."
  }
}

Fields:

  • hdr_merge: Merges bracketed exposures before editing.
  • perspective_correction: Straightens vertical and horizontal lines. Recommended for all interiors.
  • sky_replacement: Replaces blown-out skies with your account’s default sky template. Use it for exteriors.
  • callback_url: An optional HTTPS endpoint Imagen will POST to when editing finishes. See Callbacks.
HDR output is downsized in Smart Editing

When hdr_merge=true, brackets are merged and resized before editing (the long edge is bounded for the I2I model), so the final JPEGs are not full resolution. If you need full-resolution HDR output, use the profile-based flow instead.

/edit is idempotent

Calling /edit twice on the same project returns the same success message and won’t trigger a second edit. It is safe to retry on transient failures.

5. Poll status

Smart Editing uses a single status. There are no separate edit and export steps. Fetch the project and read its status. Pass get_thumbnail=false for a faster polling response. The server skips fetching the thumbnail, so the response returns sooner.

GET /v1/i2i/projects/PROJECT_UUID?get_thumbnail=false
# get_thumbnail=false skips the thumbnail for a faster polling response
curl 'https://api.imagen-ai.com/v1/i2i/projects/$PROJECT_UUID?get_thumbnail=false' \
  --header 'x-api-key: $IMAGEN_API_KEY'
# The SDK polls the project's status with backoff until it completes.
project = await client.wait_for_i2i_completion(project_uuid)
print(project.status)  # "Completed"
// The SDK polls the project's status with backoff until it completes.
const project = await client.waitForI2iCompletion(projectUuid);
console.log(project.status); // "Completed"
// The SDK polls the project's status with backoff until it completes
// and returns the download links for the edited JPEGs.
links, err := client.WaitForI2ICompletion(ctx, projectUUID, nil)
if err != nil {
    log.Fatal(err) // a Failed status surfaces as an error
}
fmt.Printf("%d edited files ready\n", len(links.FilesList))

// For a single check without polling:
project, _ := client.GetI2IProject(ctx, projectUUID, false)
fmt.Println(project.Status) // Pending | In Progress | Completed | Failed
import java.net.URI;
import java.net.http.*;

while (true) {
    var request = HttpRequest.newBuilder()
        .uri(URI.create("https://api.imagen-ai.com/v1/i2i/projects/" + projectUuid + "?get_thumbnail=false"))
        .header("x-api-key", System.getenv("IMAGEN_API_KEY"))
        .GET()
        .build();
    var response = HttpClient.newHttpClient()
        .send(request, HttpResponse.BodyHandlers.ofString());
    String status = parseStatus(response.body());
    if ("Completed".equals(status)) break;
    if ("Failed".equals(status)) throw new RuntimeException("Edit failed — contact support");
    Thread.sleep(30_000);
}
require 'net/http'
require 'json'
require 'uri'

loop do
  uri = URI("https://api.imagen-ai.com/v1/i2i/projects/#{project_uuid}?get_thumbnail=false")
  req = Net::HTTP::Get.new(uri)
  req['x-api-key'] = ENV['IMAGEN_API_KEY']

  res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
  status = JSON.parse(res.body)['data']['status']

  break if status == 'Completed'
  raise 'Edit failed — contact support' if status == 'Failed'
  sleep 30
end
<?php
while (true) {
  $ch = curl_init('https://api.imagen-ai.com/v1/i2i/projects/' . $projectUuid . '?get_thumbnail=false');
  curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ['x-api-key: ' . getenv('IMAGEN_API_KEY')],
  ]);
  $status = json_decode(curl_exec($ch), true)['data']['status'];
  curl_close($ch);

  if ($status === 'Completed') break;
  if ($status === 'Failed') throw new RuntimeException('Edit failed — contact support');
  sleep(30);
}
using System.Net.Http;
using System.Text.Json;

using var client = new HttpClient();
client.DefaultRequestHeaders.Add("x-api-key",
    Environment.GetEnvironmentVariable("IMAGEN_API_KEY"));

while (true) {
    var response = await client.GetAsync(
        $"https://api.imagen-ai.com/v1/i2i/projects/{projectUuid}?get_thumbnail=false");
    var json = await response.Content.ReadAsStringAsync();
    var status = JsonDocument.Parse(json)
        .RootElement.GetProperty("data").GetProperty("status").GetString();

    if (status == "Completed") break;
    if (status == "Failed") throw new Exception("Edit failed — contact support");
    await Task.Delay(TimeSpan.FromSeconds(30));
}
{
  "data": {
    "project_id": 42,
    "project_uuid": "65f3a1c2d4e5f6a7b8c9d0e1",
    "name": "Real Estate Listing 1734567890",
    "status": "In Progress",
    "created_at": "2026-05-31T10:42:11Z",
    "number_of_images": 42,
    "profile": null,
    "ai_tools": ["hdr_merge", "perspective_correction", "sky_replacement"],
    "customer_reference_id": 1234,
    "thumbnail_src": null,
    "export_status": null
  }
}

The possible statuses are Pending, In Progress, Completed, and Failed. Poll every 30 seconds, or skip polling entirely by passing a callback_url on /edit.

6. Download the edited JPEGs

Once status is Completed, download all output files in one call. Unlike the profile-based flow, this endpoint does not paginate. Every output file is returned in a single response.

GET /v1/i2i/projects/PROJECT_UUID/get_temporary_download_links
curl 'https://api.imagen-ai.com/v1/i2i/projects/$PROJECT_UUID/get_temporary_download_links' \
  --header 'x-api-key: $IMAGEN_API_KEY'
links = await client.get_i2i_download_links(project_uuid)
# links is a list of presigned JPEG download URLs
await client.download_files(links, output_dir="./edited")
const links = await client.getI2iDownloadLinks(projectUuid);
// links is an array of presigned JPEG download URLs
await client.downloadFiles(links, './edited');
links, _ := client.GetI2IDownloadLinks(ctx, projectUUID)
// links.FilesList pairs each file name with a presigned JPEG download URL
files, _ := client.DownloadFiles(ctx, links.FilesList, "edited", nil)
fmt.Printf("saved %d JPEGs\n", len(files))
import java.net.URI;
import java.net.http.*;

var request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.imagen-ai.com/v1/i2i/projects/" + projectUuid + "/get_temporary_download_links"))
    .header("x-api-key", System.getenv("IMAGEN_API_KEY"))
    .GET()
    .build();
var response = HttpClient.newHttpClient()
    .send(request, HttpResponse.BodyHandlers.ofString());
// Parse response.body() to read data.files_list
require 'net/http'
require 'json'
require 'uri'

uri = URI("https://api.imagen-ai.com/v1/i2i/projects/#{project_uuid}/get_temporary_download_links")
req = Net::HTTP::Get.new(uri)
req['x-api-key'] = ENV['IMAGEN_API_KEY']

res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
files = JSON.parse(res.body)['data']['files_list']
<?php
$ch = curl_init('https://api.imagen-ai.com/v1/i2i/projects/' . $projectUuid . '/get_temporary_download_links');
curl_setopt_array($ch, [
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ['x-api-key: ' . getenv('IMAGEN_API_KEY')],
]);
$response = curl_exec($ch);
curl_close($ch);
$files = json_decode($response, true)['data']['files_list'];
using System.Net.Http;
using System.Text.Json;

using var client = new HttpClient();
client.DefaultRequestHeaders.Add("x-api-key",
    Environment.GetEnvironmentVariable("IMAGEN_API_KEY"));

var response = await client.GetAsync(
    $"https://api.imagen-ai.com/v1/i2i/projects/{projectUuid}/get_temporary_download_links");
var json = await response.Content.ReadAsStringAsync();
// Parse json to read data.files_list
{
  "data": {
    "files_list": [
      { "file_name": "DSC_0001.JPEG", "download_link": "https://<s3-presigned>" }
    ]
  }
}

Grab a single file by name

GET /v1/i2i/projects/PROJECT_UUID/get_download_link?file_name=DSC_0001.JPEG
curl 'https://api.imagen-ai.com/v1/i2i/projects/$PROJECT_UUID/get_download_link?file_name=DSC_0001.JPEG' \
  --header 'x-api-key: $IMAGEN_API_KEY'
result = await client.get_i2i_download_link(project_uuid, "DSC_0001.JPEG")
link = result.download_link
const result = await client.getI2iDownloadLink(projectUuid, 'DSC_0001.JPEG');
const link = result.downloadLink;
link, _ := client.GetI2IDownloadLink(ctx, projectUUID, "DSC_0001.JPEG")
fmt.Println(link) // a presigned GET URL for that one file
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.*;
import java.nio.charset.StandardCharsets;

var uri = URI.create("https://api.imagen-ai.com/v1/i2i/projects/" + projectUuid
    + "/get_download_link?file_name="
    + URLEncoder.encode("DSC_0001.JPEG", StandardCharsets.UTF_8));
var request = HttpRequest.newBuilder()
    .uri(uri)
    .header("x-api-key", System.getenv("IMAGEN_API_KEY"))
    .GET()
    .build();
var response = HttpClient.newHttpClient()
    .send(request, HttpResponse.BodyHandlers.ofString());
require 'net/http'
require 'json'
require 'uri'

uri = URI("https://api.imagen-ai.com/v1/i2i/projects/#{project_uuid}/get_download_link")
uri.query = URI.encode_www_form(file_name: 'DSC_0001.JPEG')
req = Net::HTTP::Get.new(uri)
req['x-api-key'] = ENV['IMAGEN_API_KEY']

res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
link = JSON.parse(res.body)['data']
<?php
$url = 'https://api.imagen-ai.com/v1/i2i/projects/' . $projectUuid . '/get_download_link?'
  . http_build_query(['file_name' => 'DSC_0001.JPEG']);
$ch = curl_init($url);
curl_setopt_array($ch, [
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ['x-api-key: ' . getenv('IMAGEN_API_KEY')],
]);
$response = curl_exec($ch);
curl_close($ch);
$link = json_decode($response, true)['data'];
using System.Net.Http;

using var client = new HttpClient();
client.DefaultRequestHeaders.Add("x-api-key",
    Environment.GetEnvironmentVariable("IMAGEN_API_KEY"));

var response = await client.GetAsync(
    $"https://api.imagen-ai.com/v1/i2i/projects/{projectUuid}/get_download_link?file_name={Uri.EscapeDataString("DSC_0001.JPEG")}");
var json = await response.Content.ReadAsStringAsync();

Override an output file

To replace an output file after a manual touch-up, request a presigned PUT URL into the output bucket and PUT the new bytes. This endpoint is available only after the project reaches Completed.

GET /v1/i2i/projects/PROJECT_UUID/get_upload_link?file_name=DSC_0001.JPEG
# 1. Request a presigned PUT URL into the output bucket
curl 'https://api.imagen-ai.com/v1/i2i/projects/$PROJECT_UUID/get_upload_link?file_name=DSC_0001.JPEG' \
  --header 'x-api-key: $IMAGEN_API_KEY'

# 2. PUT the new bytes to that link with an empty Content-Type (see Uploading)
curl -X PUT "<presigned_upload_link>" \
  -H 'Content-Type;' \
  --upload-file DSC_0001.JPEG
import httpx

# 1. Get a presigned PUT URL into the output bucket
result = await client.get_i2i_upload_link(project_uuid, "DSC_0001.JPEG")

# 2. PUT the new bytes with an empty Content-Type (see Uploading)
with open("DSC_0001.JPEG", "rb") as f:
    async with httpx.AsyncClient() as http:
        await http.put(result.upload_link, content=f.read(), headers={"Content-Type": ""})
import { readFile } from 'fs/promises';

// 1. Get a presigned PUT URL into the output bucket
const { uploadLink } = await client.getI2iUploadLink(projectUuid, 'DSC_0001.JPEG');

// 2. PUT the new bytes with an empty Content-Type (see Uploading)
await fetch(uploadLink, {
  method: 'PUT',
  headers: { 'Content-Type': '' },
  body: await readFile('DSC_0001.JPEG'),
});
// Requests the presigned link and PUTs the file for you;
// files over 64 MB automatically use S3 multipart upload.
summary, _ := client.UploadI2IImages(ctx, projectUUID,
    []string{"DSC_0001.JPEG"}, nil)
fmt.Printf("Uploaded %d/%d\n", summary.Successful, summary.Total)
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;

var client = HttpClient.newHttpClient();
var linkUri = URI.create("https://api.imagen-ai.com/v1/i2i/projects/" + projectUuid
    + "/get_upload_link?file_name="
    + URLEncoder.encode("DSC_0001.JPEG", StandardCharsets.UTF_8));
var linkResp = client.send(HttpRequest.newBuilder().uri(linkUri)
    .header("x-api-key", System.getenv("IMAGEN_API_KEY")).GET().build(),
    HttpResponse.BodyHandlers.ofString());
String uploadLink = parseData(linkResp.body());

client.send(HttpRequest.newBuilder().uri(URI.create(uploadLink))
    .header("Content-Type", "")
    .PUT(HttpRequest.BodyPublishers.ofFile(Path.of("DSC_0001.JPEG"))).build(),
    HttpResponse.BodyHandlers.ofString());
require 'net/http'
require 'json'
require 'uri'

link_uri = URI("https://api.imagen-ai.com/v1/i2i/projects/#{project_uuid}/get_upload_link")
link_uri.query = URI.encode_www_form(file_name: 'DSC_0001.JPEG')
link_req = Net::HTTP::Get.new(link_uri)
link_req['x-api-key'] = ENV['IMAGEN_API_KEY']
link = JSON.parse(
  Net::HTTP.start(link_uri.hostname, link_uri.port, use_ssl: true) { |h| h.request(link_req) }.body
)['data']

put_uri = URI(link)
put_req = Net::HTTP::Put.new(put_uri)
put_req['Content-Type'] = ''
put_req.body = File.binread('DSC_0001.JPEG')
Net::HTTP.start(put_uri.hostname, put_uri.port, use_ssl: true) { |h| h.request(put_req) }
<?php
$linkUrl = 'https://api.imagen-ai.com/v1/i2i/projects/' . $projectUuid . '/get_upload_link?'
  . http_build_query(['file_name' => 'DSC_0001.JPEG']);
$ch = curl_init($linkUrl);
curl_setopt_array($ch, [
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ['x-api-key: ' . getenv('IMAGEN_API_KEY')],
]);
$link = json_decode(curl_exec($ch), true)['data'];
curl_close($ch);

$ch = curl_init($link);
curl_setopt_array($ch, [
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_CUSTOMREQUEST => 'PUT',
  CURLOPT_HTTPHEADER => ['Content-Type:'],
  CURLOPT_POSTFIELDS => file_get_contents('DSC_0001.JPEG'),
]);
curl_exec($ch);
curl_close($ch);
using System.Net.Http;
using System.Text.Json;

using var client = new HttpClient();
client.DefaultRequestHeaders.Add("x-api-key",
    Environment.GetEnvironmentVariable("IMAGEN_API_KEY"));

var linkJson = await client.GetStringAsync(
    $"https://api.imagen-ai.com/v1/i2i/projects/{projectUuid}/get_upload_link?file_name={Uri.EscapeDataString("DSC_0001.JPEG")}");
var link = JsonDocument.Parse(linkJson).RootElement.GetProperty("data").GetString();

var content = new ByteArrayContent(await File.ReadAllBytesAsync("DSC_0001.JPEG"));
content.Headers.ContentType = null; // empty Content-Type for the presigned PUT
await client.PutAsync(link, content);

List your Smart Editing projects

Browse your Smart Editing projects. Results are paginated and support filtering by archive status.

GET /v1/i2i/projects?size=20&page=0&is_archived=false

Query parameters: size (1-100, default 20), page (>= 0, default 0), is_archived (default false).

curl 'https://api.imagen-ai.com/v1/i2i/projects?size=20&page=0&is_archived=false' \
  --header 'x-api-key: $IMAGEN_API_KEY'
listing = await client.list_i2i_projects(size=20, page=0, is_archived=False)
projects, pagination = listing.projects, listing.pagination
const listing = await client.listI2iProjects({ size: 20, page: 0, isArchived: false });
const { projects, pagination } = listing;
import imagen "github.com/imagenai/imagen-ai-sdk/sdks/go"

listing, _ := client.ListI2IProjects(ctx, &imagen.ListProjectsOptions{
    Size:       imagen.Int(20),
    Page:       imagen.Int(0),
    IsArchived: imagen.Bool(false),
})
fmt.Println(listing.Pagination.Total, "projects")
import java.net.URI;
import java.net.http.*;

var request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.imagen-ai.com/v1/i2i/projects?size=20&page=0&is_archived=false"))
    .header("x-api-key", System.getenv("IMAGEN_API_KEY"))
    .GET()
    .build();
var response = HttpClient.newHttpClient()
    .send(request, HttpResponse.BodyHandlers.ofString());
require 'net/http'
require 'json'
require 'uri'

uri = URI("https://api.imagen-ai.com/v1/i2i/projects")
uri.query = URI.encode_www_form(size: 20, page: 0, is_archived: false)
req = Net::HTTP::Get.new(uri)
req['x-api-key'] = ENV['IMAGEN_API_KEY']

res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)['data']
<?php
$url = 'https://api.imagen-ai.com/v1/i2i/projects?'
  . http_build_query(['size' => 20, 'page' => 0, 'is_archived' => 'false']);
$ch = curl_init($url);
curl_setopt_array($ch, [
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ['x-api-key: ' . getenv('IMAGEN_API_KEY')],
]);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true)['data'];
using System.Net.Http;

using var client = new HttpClient();
client.DefaultRequestHeaders.Add("x-api-key",
    Environment.GetEnvironmentVariable("IMAGEN_API_KEY"));

var response = await client.GetAsync(
    "https://api.imagen-ai.com/v1/i2i/projects?size=20&page=0&is_archived=false");
var json = await response.Content.ReadAsStringAsync();
{
  "data": {
    "projects": [
      {
        "project_id": 42,
        "project_uuid": "65f3a1c2d4e5f6a7b8c9d0e1",
        "name": "Real Estate Listing 1734567890",
        "status": "Completed",
        "created_at": "2026-05-31T10:42:11Z",
        "number_of_images": 42,
        "profile": null,
        "ai_tools": ["hdr_merge", "perspective_correction"],
        "customer_reference_id": 1234,
        "thumbnail_src": "https://<s3-presigned>",
        "export_status": null
      }
    ],
    "pagination": { "total": 137, "size": 20, "page": 0 }
  }
}