---
title: "Smart Editing (I2I)"
description: "Reference for the Imagen Smart Editing (image-to-image) API: create an I2I project, upload, run a profile-free edit, poll one status, and download edited JPEGs."
canonical: https://api-docs.imagen-ai.com/docs/reference/smart-editing/
last-updated: 2026-08-27
---

# 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](/docs/quickstart), 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](/docs/guides/real-estate).

## 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
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"}'
```

```python
project_uuid = await client.create_i2i_project("Real Estate Listing 1734567890")
```

```typescript
const projectUuid = await client.createI2iProject('Real Estate Listing 1734567890');
```

```go
projectUUID, _ := client.CreateI2IProject(ctx, "Real Estate Listing 1734567890")
```

```java
import java.net.URI;

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());
```

```ruby
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
<?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'];
```

```csharp
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();
```
```json
{
  "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
curl 'https://api.imagen-ai.com/v1/i2i/projects/is_valid_name?name=$NAME' \
  --header 'x-api-key: $IMAGEN_API_KEY'
```

```python
is_valid = await client.validate_i2i_project_name(name)
```

```typescript
const isValid = await client.validateI2iProjectName(name);
```

```go
isValid, _ := client.IsValidI2IName(ctx, name)
```

```java
import java.net.URI;

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());
```

```ruby
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
<?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'];
```

```csharp
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();
```
```json
{
  "data": {
    "is_valid": true
  }
}
```

## 2. Request upload links

`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](/docs/reference/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](/docs/quickstart) flow and are
documented once on the [Uploading](/docs/reference/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
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
  }'
```

```python
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)
```

```typescript
const result = await client.startI2iEditing(projectUuid, {
  hdr_merge: true,
  perspective_correction: true,
  sky_replacement: true,
});
console.log(result.message);
```

```go
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)
}
```

```java
import java.net.URI;

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());
```

```ruby
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
<?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);
```

```csharp
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);
```
```json
{
  "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](/docs/reference/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](/docs/quickstart) 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`

```curl
# 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'
```

```python
# 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"
```

```typescript
// The SDK polls the project's status with backoff until it completes.
const project = await client.waitForI2iCompletion(projectUuid);
console.log(project.status); // "Completed"
```

```go
// 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
```

```java
import java.net.URI;

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);
}
```

```ruby
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
<?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);
}
```

```csharp
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));
}
```
```json
{
  "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
curl 'https://api.imagen-ai.com/v1/i2i/projects/$PROJECT_UUID/get_temporary_download_links' \
  --header 'x-api-key: $IMAGEN_API_KEY'
```

```python
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")
```

```typescript
const links = await client.getI2iDownloadLinks(projectUuid);
// links is an array of presigned JPEG download URLs
await client.downloadFiles(links, './edited');
```

```go
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))
```

```java
import java.net.URI;

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
```

```ruby
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
<?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'];
```

```csharp
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
```
```json
{
  "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
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'
```

```python
result = await client.get_i2i_download_link(project_uuid, "DSC_0001.JPEG")
link = result.download_link
```

```typescript
const result = await client.getI2iDownloadLink(projectUuid, 'DSC_0001.JPEG');
const link = result.downloadLink;
```

```go
link, _ := client.GetI2IDownloadLink(ctx, projectUUID, "DSC_0001.JPEG")
fmt.Println(link) // a presigned GET URL for that one file
```

```java
import java.net.URI;

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());
```

```ruby
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
<?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'];
```

```csharp
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`

```curl
# 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
```

```python
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": ""})
```

```typescript
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'),
});
```

```go
// 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)
```

```java
import java.net.URI;

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());
```

```ruby
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
<?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);
```

```csharp
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
curl 'https://api.imagen-ai.com/v1/i2i/projects?size=20&page=0&is_archived=false' \
  --header 'x-api-key: $IMAGEN_API_KEY'
```

```python
listing = await client.list_i2i_projects(size=20, page=0, is_archived=False)
projects, pagination = listing.projects, listing.pagination
```

```typescript
const listing = await client.listI2iProjects({ size: 20, page: 0, isArchived: false });
const { projects, pagination } = listing;
```

```go
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")
```

```java
import java.net.URI;

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());
```

```ruby
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
<?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'];
```

```csharp
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();
```
```json
{
  "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 }
  }
}
```
