Imagen API docs
Navigation menu
First steps

Quickstart to Imagen API

Use our quickstart to learn how the Imagen API works and to customize your flow.

Complete the onboarding first

You need to sign up for Imagen, set up your environment, choose an AI Profile, and a few other steps before using this quickstart. See Onboarding. If you want to use the Imagen API with little coding, here’s the SDK in Python.

  1. Get a list of available AI Profiles

    Get a list of AI Profiles and their keys.

    GET /v1/profiles/

    Response example

    The response includes the available AI Profiles. Each AI Profile has a profile_key that identifies it. You need this profile_key to edit the photos in your project.

    curl 'https://api.imagen-ai.com/v1/profiles/' \
      --header 'x-api-key: $IMAGEN_API_KEY'
    profiles = await client.get_profiles()
    for p in profiles:
        print(f"{p.profile_name} — key: {p.profile_key}")
    # → wedding — key: 5700
    const res = await fetch('https://api.imagen-ai.com/v1/profiles/', {
      headers: { 'x-api-key': process.env.IMAGEN_API_KEY! },
    });
    const { data } = (await res.json()) as { data: { profiles: ProfileItem[] } };
    for (const p of data.profiles) {
      console.log(`${p.profile_name} — key: ${p.profile_key}`);
    }
    import (
        "encoding/json"
        "net/http"
        "os"
    )
    
    req, _ := http.NewRequestWithContext(ctx, http.MethodGet,
        "https://api.imagen-ai.com/v1/profiles/", nil)
    req.Header.Set("x-api-key", os.Getenv("IMAGEN_API_KEY"))
    
    resp, _ := http.DefaultClient.Do(req)
    defer resp.Body.Close()
    
    var result struct {
        Data struct {
            Profiles []struct {
                ProfileKey  int    `json:"profile_key"`
                ProfileName string `json:"profile_name"`
            } `json:"profiles"`
        } `json:"data"`
    }
    json.NewDecoder(resp.Body).Decode(&result)
    import java.net.URI;
    import java.net.http.*;
    
    var request = HttpRequest.newBuilder()
        .uri(URI.create("https://api.imagen-ai.com/v1/profiles/"))
        .header("x-api-key", System.getenv("IMAGEN_API_KEY"))
        .GET()
        .build();
    var response = HttpClient.newHttpClient()
        .send(request, HttpResponse.BodyHandlers.ofString());
    // response.body() contains the JSON
    require 'net/http'
    require 'json'
    require 'uri'
    
    uri = URI("https://api.imagen-ai.com/v1/profiles/")
    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) }
    profiles = JSON.parse(res.body)['data']['profiles']
    profiles.each { |p| puts "#{p['profile_name']} — key: #{p['profile_key']}" }
    <?php
    $ch = curl_init('https://api.imagen-ai.com/v1/profiles/');
    curl_setopt_array($ch, [
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_HTTPHEADER => [
        'x-api-key: ' . getenv('IMAGEN_API_KEY'),
      ],
    ]);
    $response = curl_exec($ch);
    curl_close($ch);
    $profiles = json_decode($response, true)['data']['profiles'];
    foreach ($profiles as $p) {
      echo $p['profile_name'] . ' — key: ' . $p['profile_key'] . PHP_EOL;
    }
    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/profiles/");
    var json = await response.Content.ReadAsStringAsync();
    // Parse json to access data.profiles
    {
      "data": {
        "profiles": [
          {
            "profile_key": <profile key of the Personal AI Profile>,
            "profile_name": "wedding",
            "profile_type": "Personal",
            "image_type": "RAW"
          },
          {
            "profile_key": 14715,
            "profile_name": "LOVE & LIGHT",
            "profile_type": "Talent",
            "image_type": "RAW"
          },
          {
            "profile_key": 163322,
            "profile_name": "ELEGANT HOME",
            "profile_type": "Talent",
            "image_type": "RAW"
          }
        ]
      }
    }
  2. Cull your photos without the API

    Cull your photos before uploading them for editing. Culling isn’t part of the Imagen API.

  3. Create a project and get a project ID

    We recommend creating a project for each shoot. A project organizes your photos with the AI Profile you chose for editing these photos.

    POST /v1/projects/

    Response example

    The response is the project_uuid used to identify this project. Use this project_uuid until you export your photos to JPEG.

    {
      "data": {
        "project_uuid": "<project_uuid>"
      }
    }
  4. Upload each photo to its temporary upload link

    Upload photos with a PUT request to Imagen’s S3 bucket on AWS. Use the temporary upload links from the response in the previous step.

    Content-Type requirements

    Testing in Postman: Disable Content-Type in the headers. Do not send Content-Type in the query params.


    In code: Either omit Content-Type entirely, or set it to an empty string (""). Both work — see the language tabs above for the idiomatic approach per language.

    # PUT directly to the presigned URL — no x-api-key needed
    # The -H 'Content-Type;' flag removes the Content-Type header
    curl -X PUT "<presigned_upload_link>" \
      -H 'Content-Type;' \
      --upload-file 922A4846.CR2
    import httpx
    
    async def upload_one(path: str, url: str):
        with open(path, "rb") as f:
            content = f.read()
        # Content-Type must be empty — no x-api-key needed for S3
        async with httpx.AsyncClient() as http:
            await http.put(url, content=content, headers={"Content-Type": ""})
    
    # The SDK's upload_images() handles all of this concurrently for you
    import { readFile } from 'fs/promises';
    
    const fileContent = await readFile('922A4846.CR2');
    
    // No x-api-key needed for S3 presigned URLs — Content-Type must be empty
    await fetch(presignedUploadLink, {
      method: 'PUT',
      headers: { 'Content-Type': '' },
      body: fileContent,
    });
    import (
        "bytes"
        "net/http"
        "os"
    )
    
    fileContent, _ := os.ReadFile("922A4846.CR2")
    req, _ := http.NewRequestWithContext(ctx, http.MethodPut,
        presignedUploadLink,
        bytes.NewReader(fileContent),
    )
    // No x-api-key for S3 presigned URLs — Content-Type must be empty
    req.Header.Set("Content-Type", "")
    
    resp, _ := http.DefaultClient.Do(req)
    defer resp.Body.Close()
    import java.net.URI;
    import java.net.http.*;
    import java.nio.file.*;
    
    var fileBytes = Files.readAllBytes(Path.of("922A4846.CR2"));
    // No x-api-key for S3 presigned URLs — Content-Type must be empty
    var request = HttpRequest.newBuilder()
        .uri(URI.create(presignedUploadLink))
        .header("Content-Type", "")
        .PUT(HttpRequest.BodyPublishers.ofByteArray(fileBytes))
        .build();
    var response = HttpClient.newHttpClient()
        .send(request, HttpResponse.BodyHandlers.ofString());
    require 'net/http'
    require 'uri'
    
    uri = URI(presigned_upload_link)
    file_content = File.binread('922A4846.CR2')
    
    req = Net::HTTP::Put.new(uri)
    
    # No x-api-key for S3 presigned URLs — Content-Type must be empty
    
    req['Content-Type'] = ''
    req.body = file_content
    
    Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
    <?php
    $fileContent = file_get_contents('922A4846.CR2');
    $ch = curl_init($presignedUploadLink);
    curl_setopt_array($ch, [
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_CUSTOMREQUEST => 'PUT',
      // No x-api-key for S3 presigned URLs — Content-Type must be empty
      CURLOPT_HTTPHEADER => ['Content-Type:'],
      CURLOPT_POSTFIELDS => $fileContent,
    ]);
    curl_exec($ch);
    curl_close($ch);
    using System.Net.Http;
    using System.IO;
    
    using var client = new HttpClient();
    var fileBytes = await File.ReadAllBytesAsync("922A4846.CR2");
    
    // No x-api-key for S3 presigned URLs — pass "" for empty Content-Type
    var content = new ByteArrayContent(fileBytes);
    content.Headers.ContentType = null;
    
    var response = await client.PutAsync(presignedUploadLink, content);
  5. Edit the photos in your project

    Before sending your photos to edit, you need:

    POST /v1/projects/PROJECT_UUID/edit
    Content-Type must be empty

    This endpoint requires an empty Content-Type header. The Python SDK handles this automatically.

    curl -X POST \
      'https://api.imagen-ai.com/v1/projects/$PROJECT_UUID/edit' \
      --header 'x-api-key: $IMAGEN_API_KEY' \
      --header 'Content-Type;' \
      --data '{
        "profile_key": 163322,
        "hdr_merge": true,
        "photography_type": "REAL_ESTATE",
        "callback_url": "https://your.app/imagen/hook",
        "perspective_correction": true,
        "sky_replacement": true,
        "sky_replacement_template_id": 2
      }'
    from imagen_sdk import EditOptions, PhotographyType
    
    await client.start_editing(
        project_uuid,
        profile_key=163322,
        photography_type=PhotographyType.REAL_ESTATE,
        edit_options=EditOptions(
            hdr_merge=True,
            perspective_correction=True,
            sky_replacement=True,
            sky_replacement_template_id=2,
        ),
    )
    const body = {
      profile_key: 163322,
      hdr_merge: true,
      photography_type: 'REAL_ESTATE',
      callback_url: 'https://your.app/imagen/hook',
      perspective_correction: true,
      sky_replacement: true,
      sky_replacement_template_id: 2,
    };
    
    const res = await fetch(
      `https://api.imagen-ai.com/v1/projects/${projectUuid}/edit`,
      {
        method: 'POST',
        headers: {
          'x-api-key': process.env.IMAGEN_API_KEY!,
          'Content-Type': '',
        },
        body: JSON.stringify(body),
      }
    );
    
    const { data } = (await res.json()) as { data: EditResponse };
    import (
        "bytes"
        "encoding/json"
        "net/http"
        "os"
    )
    
    body, _ := json.Marshal(map[string]any{
        "profile_key": 163322,
        "hdr_merge": true,
        "photography_type": "REAL_ESTATE",
        "callback_url": "https://your.app/imagen/hook",
        "perspective_correction": true,
        "sky_replacement": true,
        "sky_replacement_template_id": 2,
    })
    req, _ := http.NewRequestWithContext(ctx, http.MethodPost,
        "https://api.imagen-ai.com/v1/projects/"+projectUUID+"/edit",
        bytes.NewReader(body),
    )
    req.Header.Set("x-api-key", os.Getenv("IMAGEN_API_KEY"))
    req.Header.Set("Content-Type", "")
    
    resp, _ := http.DefaultClient.Do(req)
    defer resp.Body.Close()
    import java.net.URI;
    import java.net.http.*;
    
    var body = """
        {
            "profile_key": 163322,
            "hdr_merge": true,
            "photography_type": "REAL_ESTATE",
            "callback_url": "https://your.app/imagen/hook",
            "perspective_correction": true,
            "sky_replacement": true,
            "sky_replacement_template_id": 2
        }
        """;
    var request = HttpRequest.newBuilder()
        .uri(URI.create("https://api.imagen-ai.com/v1/projects/" + projectUuid + "/edit"))
        .header("x-api-key", System.getenv("IMAGEN_API_KEY"))
        .header("Content-Type", "")
        .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/projects/#{project_uuid}/edit")
    req = Net::HTTP::Post.new(uri)
    req['x-api-key'] = ENV['IMAGEN_API_KEY']
    req['Content-Type'] = ''
    req.body = {
      profile_key: 163322,
      hdr_merge: true,
      photography_type: 'REAL_ESTATE',
      callback_url: 'https://your.app/imagen/hook',
      perspective_correction: true,
      sky_replacement: true,
      sky_replacement_template_id: 2,
    }.to_json
    
    res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
    data = JSON.parse(res.body)['data']
    <?php
    $ch = curl_init('https://api.imagen-ai.com/v1/projects/' . $projectUuid . '/edit');
    curl_setopt_array($ch, [
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_POST => true,
      CURLOPT_HTTPHEADER => [
        'x-api-key: ' . getenv('IMAGEN_API_KEY'),
        'Content-Type:',
      ],
      CURLOPT_POSTFIELDS => json_encode([
        'profile_key' => 163322,
        'hdr_merge' => true,
        'photography_type' => 'REAL_ESTATE',
        'callback_url' => 'https://your.app/imagen/hook',
        'perspective_correction' => true,
        'sky_replacement' => true,
        'sky_replacement_template_id' => 2,
      ]),
    ]);
    $response = curl_exec($ch);
    curl_close($ch);
    $data = json_decode($response, true)['data'];
    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 {
        profile_key = 163322,
        hdr_merge = true,
        photography_type = "REAL_ESTATE",
        callback_url = "https://your.app/imagen/hook",
        perspective_correction = true,
        sky_replacement = true,
        sky_replacement_template_id = 2,
    });
    // Pass "" as the media type to send an empty Content-Type header
    var content = new StringContent(payload, Encoding.UTF8, "");
    var response = await client.PostAsync(
        $"https://api.imagen-ai.com/v1/projects/{projectUuid}/edit",
        content
    );
    var json = await response.Content.ReadAsStringAsync();

    The response is a message that the project was edited successfully. If there is an error, email our customer success team.

    {"project_uuid": "<the project UUID>", "status": "Failed/Completed/Pending", "action": "edit/export"}

    Add a callback URL to get status messages

    The callback_url is a POST endpoint that Imagen can use to send you status messages. This param is optional. If you use it, you don’t need to check the edit status. This endpoint must be open to Imagen.

    Possible statuses sent in the request are:

    • Failed: Email the customer success team.
    • Completed: Imagen API has finished editing the photos. Continue to one of the following: Get temporary download links to download edited photos or XMPs to tweak edits and export photos with your editing software, OR Export photos for delivery to create exported JPEGs for customers.
  6. Check the edit status

    If you didn’t add a callback URL to your edit request, get the current status of the editing process. Continue calling this endpoint with long polling until the status is Completed.

    GET /v1/projects/PROJECT_UUID/edit/status

    Here are the statuses in the response:

    StatusWhat it means
    Pending

    The editing process hasn’t started. Once the editing starts, the status will change to In Progress.

    In ProgressImagen API is in the process of editing the photos.
    FailedEmail the customer success team.
    Completed

    Imagen API has finished editing the photos. Continue to step 8 or step 10.

    # Poll until status is "Completed" or "Failed"
    curl 'https://api.imagen-ai.com/v1/projects/$PROJECT_UUID/edit/status' \
      --header 'x-api-key: $IMAGEN_API_KEY'
    # The SDK polls edit status internally inside start_editing()
    # To check the raw status endpoint yourself:
    import httpx, os
    
    async with httpx.AsyncClient() as http:
        resp = await http.get(
            f"https://api.imagen-ai.com/v1/projects/{project_uuid}/edit/status",
            headers={"x-api-key": os.environ["IMAGEN_API_KEY"]},
        )
        data = resp.json()["data"]
        print(data["status"])  # Pending | In Progress | Completed | Failed
    // Start at 10 s, back off ×1.2 each poll, cap at 60 s
    async function waitForEdit(projectUuid: string): Promise<void> {
      let interval = 10_000;
      while (true) {
        const res = await fetch(
          `https://api.imagen-ai.com/v1/projects/${projectUuid}/edit/status`,
          { headers: { 'x-api-key': process.env.IMAGEN_API_KEY! } }
        );
        const { data } = (await res.json()) as { data: { status: string } };
        console.log(`Status: ${data.status}`);
        if (data.status === 'Completed') return;
        if (data.status === 'Failed') throw new Error('Edit failed — contact support');
        await new Promise(r => setTimeout(r, interval));
        interval = Math.min(interval * 1.2, 60_000);
      }
    }
    
    await waitForEdit(projectUuid);
    import (
    "encoding/json"
    "net/http"
    "os"
    "time"
    )
    
    interval := 10 * time.Second
    for {
        req, _ := http.NewRequestWithContext(ctx, http.MethodGet,
            "https://api.imagen-ai.com/v1/projects/"+projectUUID+"/edit/status", nil)
        req.Header.Set("x-api-key", os.Getenv("IMAGEN_API_KEY"))
    
        resp, _ := http.DefaultClient.Do(req)
        var result struct {
            Data struct{ Status string `json:"status"` } `json:"data"`
        }
        json.NewDecoder(resp.Body).Decode(&result)
        resp.Body.Close()
    
        switch result.Data.Status {
        case "Completed":
            return nil
        case "Failed":
            return fmt.Errorf("edit failed — contact support")
        }
        time.Sleep(interval)
        if interval < 60*time.Second {
            interval = time.Duration(float64(interval) * 1.2)
        }
    
    }
    import java.net.URI;
    import java.net.http.*;
    
    long interval = 10_000;
    while (true) {
        var request = HttpRequest.newBuilder()
            .uri(URI.create("https://api.imagen-ai.com/v1/projects/" + projectUuid + "/edit/status"))
            .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(interval);
        interval = Math.min((long)(interval * 1.2), 60_000);
    }
    require 'net/http'
    require 'json'
    require 'uri'
    
    interval = 10
    loop do
      uri = URI("https://api.imagen-ai.com/v1/projects/#{project_uuid}/edit/status")
      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']
      puts "Status: #{status}"
    
      break if status == 'Completed'
      raise 'Edit failed — contact support' if status == 'Failed'
      sleep interval
      interval = [interval * 1.2, 60].min
    end
    <?php
    $interval = 10;
    while (true) {
      $ch = curl_init('https://api.imagen-ai.com/v1/projects/' . $projectUuid . '/edit/status');
      curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER => ['x-api-key: ' . getenv('IMAGEN_API_KEY')],
      ]);
      $response = curl_exec($ch);
      curl_close($ch);
    
      $status = json_decode($response, true)['data']['status'];
      echo "Status: $status
    ";
    
      if ($status === 'Completed') break;
      if ($status === 'Failed') throw new RuntimeException('Edit failed — contact support');
      sleep($interval);
      $interval = min($interval * 1.2, 60);
    }
    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 interval = TimeSpan.FromSeconds(10);
    while (true) {
        var response = await client.GetAsync(
            $"https://api.imagen-ai.com/v1/projects/{projectUuid}/edit/status");
        var json = await response.Content.ReadAsStringAsync();
        var doc = JsonDocument.Parse(json);
        var status = doc.RootElement.GetProperty("data").GetProperty("status").GetString();
    
        Console.WriteLine($"Status: {status}");
        if (status == "Completed") break;
        if (status == "Failed") throw new Exception("Edit failed — contact support");
        await Task.Delay(interval);
        interval = interval.TotalSeconds < 60
            ? TimeSpan.FromSeconds(interval.TotalSeconds * 1.2)
            : TimeSpan.FromSeconds(60);
    
    }
  7. Download edited photos locally

    When Imagen returns the edited files, these are the respective formats:

    File format before editFile format returned after edit
    RAW

    XMP


    Note: When editing photos with HDR Merge, the returned format is always DNG, even though the file format before editing was RAW.

    RAW + DNGDNG with embedded XMP
    DNGDNG with embedded XMP
    JPEGJPEG with embedded XMP

    Download photos from Imagen’s S3 bucket on AWS with the temporary download links from the response in the previous step. Use any method you like.

    Review the edits in Adobe editing software. If you open the photos with the default photo viewer on your computer, you won’t see the edits.

  8. Optional: Export final JPEGs for delivery

    Imagen API exports all the photos in a project to JPEG format. This step is optional. Some photographers tweak the edits and export them with their editing software. After exporting the final JPEGs, you deliver them or upload them to a gallery.

    1. Export the photos in the project

    POST /v1/projects/PROJECT_UUID/export
    Content-Type must be empty

    This endpoint requires an empty Content-Type header. The Python SDK handles this automatically.

    curl -X POST \
      'https://api.imagen-ai.com/v1/projects/$PROJECT_UUID/export' \
      --header 'x-api-key: $IMAGEN_API_KEY' \
      --header 'Content-Type;'
    await client.export_project(project_uuid)
    // Content-Type must be empty string — required by this endpoint
    const res = await fetch(
      `https://api.imagen-ai.com/v1/projects/${projectUuid}/export`,
      {
        method: 'POST',
        headers: {
          'x-api-key': process.env.IMAGEN_API_KEY!,
          'Content-Type': '',
        },
      }
    );
    const { data } = (await res.json()) as { data: { project_uuid: string; status: string } };
    import (
        "net/http"
        "os"
    )
    
    req, _ := http.NewRequestWithContext(ctx, http.MethodPost,
        "https://api.imagen-ai.com/v1/projects/"+projectUUID+"/export", nil)
    req.Header.Set("x-api-key", os.Getenv("IMAGEN_API_KEY"))
    req.Header.Set("Content-Type", "")
    
    resp, _ := http.DefaultClient.Do(req)
    defer resp.Body.Close()
    import java.net.URI;
    import java.net.http.*;
    
    var request = HttpRequest.newBuilder()
        .uri(URI.create("https://api.imagen-ai.com/v1/projects/" + projectUuid + "/export"))
        .header("x-api-key", System.getenv("IMAGEN_API_KEY"))
        .header("Content-Type", "")
        .POST(HttpRequest.BodyPublishers.noBody())
        .build();
    var response = HttpClient.newHttpClient()
        .send(request, HttpResponse.BodyHandlers.ofString());
    require 'net/http'
    require 'uri'
    
    uri = URI("https://api.imagen-ai.com/v1/projects/#{project_uuid}/export")
    req = Net::HTTP::Post.new(uri)
    req['x-api-key'] = ENV['IMAGEN_API_KEY']
    req['Content-Type'] = ''
    
    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/projects/' . $projectUuid . '/export');
    curl_setopt_array($ch, [
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_POST => true,
      CURLOPT_HTTPHEADER => [
        'x-api-key: ' . getenv('IMAGEN_API_KEY'),
        'Content-Type:',
      ],
      CURLOPT_POSTFIELDS => '',
    ]);
    $response = curl_exec($ch);
    curl_close($ch);
    $data = json_decode($response, true)['data'];
    using System.Net.Http;
    using System.Text;
    
    using var client = new HttpClient();
    client.DefaultRequestHeaders.Add("x-api-key",
        Environment.GetEnvironmentVariable("IMAGEN_API_KEY"));
    
    // Pass "" as the media type to send an empty Content-Type header
    var content = new StringContent("", Encoding.UTF8, "");
    var response = await client.PostAsync(
        $"https://api.imagen-ai.com/v1/projects/{projectUuid}/export",
        content
    );
    var json = await response.Content.ReadAsStringAsync();

    The response includes the project_uuid and a message that the export was successful. If there is an error, email our customer success team.

    2. Check the export status

    If you didn’t add a callback URL in your edit request, get the export status.

    GET /v1/projects/PROJECT_UUID/export/status

    Possible statuses are:

    • In Progress: Imagen API is in the process of editing the photos.
    • Failed: Email the customer success team.
    • Completed: Imagen API has finished exporting the photos to JPEG.
    # Poll until status is "Completed" or "Failed"
    curl 'https://api.imagen-ai.com/v1/projects/$PROJECT_UUID/export/status' \
      --header 'x-api-key: $IMAGEN_API_KEY'
    # The SDK polls export status internally inside export_project()
    # To check the raw status endpoint yourself:
    import httpx, os
    
    async with httpx.AsyncClient() as http:
        resp = await http.get(
            f"https://api.imagen-ai.com/v1/projects/{project_uuid}/export/status",
            headers={"x-api-key": os.environ["IMAGEN_API_KEY"]},
        )
        data = resp.json()["data"]
        print(data["status"])  # In Progress | Completed | Failed
    // Same backoff pattern as edit polling
    async function waitForExport(projectUuid: string): Promise<void> {
      let interval = 10_000;
      while (true) {
        const res = await fetch(
          `https://api.imagen-ai.com/v1/projects/${projectUuid}/export/status`,
          { headers: { 'x-api-key': process.env.IMAGEN_API_KEY! } }
        );
        const { data } = (await res.json()) as { data: { status: string } };
        console.log(`Status: ${data.status}`);
        if (data.status === 'Completed') return;
        if (data.status === 'Failed') throw new Error('Export failed — contact support');
        await new Promise(r => setTimeout(r, interval));
        interval = Math.min(interval * 1.2, 60_000);
      }
    }
    
    await waitForExport(projectUuid);
    import (
    "encoding/json"
    "net/http"
    "os"
    "time"
    )
    
    interval := 10 * time.Second
    for {
        req, _ := http.NewRequestWithContext(ctx, http.MethodGet,
            "https://api.imagen-ai.com/v1/projects/"+projectUUID+"/export/status", nil)
        req.Header.Set("x-api-key", os.Getenv("IMAGEN_API_KEY"))
    
        resp, _ := http.DefaultClient.Do(req)
        var result struct {
            Data struct{ Status string `json:"status"` } `json:"data"`
        }
        json.NewDecoder(resp.Body).Decode(&result)
        resp.Body.Close()
    
        switch result.Data.Status {
        case "Completed":
            return nil
        case "Failed":
            return fmt.Errorf("export failed — contact support")
        }
        time.Sleep(interval)
        if interval < 60*time.Second {
            interval = time.Duration(float64(interval) * 1.2)
        }
    
    }
    import java.net.URI;
    import java.net.http.*;
    
    long interval = 10_000;
    while (true) {
        var request = HttpRequest.newBuilder()
            .uri(URI.create("https://api.imagen-ai.com/v1/projects/" + projectUuid + "/export/status"))
            .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("Export failed — contact support");
        Thread.sleep(interval);
        interval = Math.min((long)(interval * 1.2), 60_000);
    }
    require 'net/http'
    require 'json'
    require 'uri'
    
    interval = 10
    loop do
      uri = URI("https://api.imagen-ai.com/v1/projects/#{project_uuid}/export/status")
      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']
      puts "Status: #{status}"
    
      break if status == 'Completed'
      raise 'Export failed — contact support' if status == 'Failed'
      sleep interval
      interval = [interval * 1.2, 60].min
    end
    <?php
    $interval = 10;
    while (true) {
      $ch = curl_init('https://api.imagen-ai.com/v1/projects/' . $projectUuid . '/export/status');
      curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER => ['x-api-key: ' . getenv('IMAGEN_API_KEY')],
      ]);
      $response = curl_exec($ch);
      curl_close($ch);
    
      $status = json_decode($response, true)['data']['status'];
      echo "Status: $status
    ";
    
      if ($status === 'Completed') break;
      if ($status === 'Failed') throw new RuntimeException('Export failed — contact support');
      sleep($interval);
      $interval = min($interval * 1.2, 60);
    }
    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 interval = TimeSpan.FromSeconds(10);
    while (true) {
        var response = await client.GetAsync(
            $"https://api.imagen-ai.com/v1/projects/{projectUuid}/export/status");
        var json = await response.Content.ReadAsStringAsync();
        var doc = JsonDocument.Parse(json);
        var status = doc.RootElement.GetProperty("data").GetProperty("status").GetString();
    
        Console.WriteLine($"Status: {status}");
        if (status == "Completed") break;
        if (status == "Failed") throw new Exception("Export failed — contact support");
        await Task.Delay(interval);
        interval = interval.TotalSeconds < 60
            ? TimeSpan.FromSeconds(interval.TotalSeconds * 1.2)
            : TimeSpan.FromSeconds(60);
    
    }

    3. Get temporary download links to download final JPEG photos

    Get the temporary download links when the export status is returned as Completed.

    GET /v1/projects/PROJECT_UUID/export/get_temporary_download_links

    In the request, include the exported photos to download in the files_list param. Use list format.

    curl 'https://api.imagen-ai.com/v1/projects/$PROJECT_UUID/export/get_temporary_download_links' \
      --header 'x-api-key: $IMAGEN_API_KEY'
    links = await client.get_export_links(project_uuid)
    # links is a list of objects with .file_name and .download_link
    const res = await fetch(
      `https://api.imagen-ai.com/v1/projects/${projectUuid}/export/get_temporary_download_links`,
      { headers: { 'x-api-key': process.env.IMAGEN_API_KEY! } }
    );
    const { data } = (await res.json()) as { data: { files_list: DownloadLink[] } };
    // data.files_list: [{ file_name, download_link }, ...]
    import (
        "encoding/json"
        "net/http"
        "os"
    )
    
    req, _ := http.NewRequestWithContext(ctx, http.MethodGet,
        "https://api.imagen-ai.com/v1/projects/"+projectUUID+"/export/get_temporary_download_links", nil)
    req.Header.Set("x-api-key", os.Getenv("IMAGEN_API_KEY"))
    
    resp, _ := http.DefaultClient.Do(req)
    defer resp.Body.Close()
    
    var result struct {
        Data struct {
            FilesList []struct {
                FileName     string `json:"file_name"`
                DownloadLink string `json:"download_link"`
            } `json:"files_list"`
        } `json:"data"`
    }
    json.NewDecoder(resp.Body).Decode(&result)
    import java.net.URI;
    import java.net.http.*;
    
    var request = HttpRequest.newBuilder()
        .uri(URI.create("https://api.imagen-ai.com/v1/projects/" + projectUuid + "/export/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 extract data.files_list
    require 'net/http'
    require 'json'
    require 'uri'
    
    uri = URI("https://api.imagen-ai.com/v1/projects/#{project_uuid}/export/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']
    
    # files: [{ "file_name" => "...", "download_link" => "..." }, ...]
    <?php
    $ch = curl_init('https://api.imagen-ai.com/v1/projects/' . $projectUuid . '/export/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/projects/{projectUuid}/export/get_temporary_download_links");
    var json = await response.Content.ReadAsStringAsync();
    // Parse json to access data.files_list

    Response example

    The response includes a temporary link for each photo to Imagen’s S3 bucket on AWS. This link is the AWS presigned URL.

    {
      "data": {
        "files_list": [
          {
            "file_name": "922A4846.JPG",
            "download_link": "<presigned URL for 922A4846.JPG>"
          },
          {
            "file_name": "922A4832.JPG",
            "download_link": "<presigned URL for 922A4832.JPG>"
          },
          {
            "file_name": "922A4818.JPG",
            "download_link": "<presigned URL for 922A4818.JPG>"
          },
          {
            "file_name": "922A4809.JPG",
            "download_link": "<presigned URL for 922A4809.JPG>"
          }
        ]
      }
    }

    4. Download final JPEG photos locally

    Download photos from Imagen’s S3 bucket on AWS with the temporary download links from the response in the previous step. Use any method you like.