Garment recolor

Recolor one garment consistently across a set of product images

You will need an API token to send HTTP requests. See Authentication for instructions.

Quick start

Create a project to organize your images. The project_id will be used in subsequent requests. See Creating a project for details.


Upload the product images that contain the garment to recolor (and, optionally, a colour/pattern reference image). This is a two-step process:

  1. Request a pre-signed upload URL
  2. PUT the image binary to that URL

Collect all file_id values for the next step. See Uploading images for details.

The upload_url is only valid for a limited time. Upload the image immediately after receiving the response.


Start a Garment Recolor job with the list of uploaded file IDs and a recolor target (a reference image, a colour/pattern text, an instruction, or any combination). Each input image yields num_variations outputs. No identity is required: Garment Recolor is product-only. See Starting a job for details.


Track job progress with SSE or webhooks. Filter events with your job ID and stop when you receive a terminal status. See Tracking progress for details.

Like Create Packshot, Garment Recolor does not require an identity. It recolors the garment in your existing product images and keeps everything else untouched. If you need a model wearing the garment, use Flat-lay-to-on-model or Model Swap instead.

Creating a project

import requests

api_url = "https://v2.api.piktid.com"
access_token = "your_access_token"

response = requests.post(
    api_url + "/project",
    headers={"Authorization": "Bearer " + access_token},
    json={"project_name": "my-recolor-project"},
).json()
project_id = response["project_id"]
project_name = response["project_name"]
Response
{
  "project_id": "abc123...",   // PROJECT_ID
  "project_name": "my-recolor-project"
}

Uploading images

Upload the product images that show the garment you want to recolor. For a consistent result, all images should feature the same garment (different poses, angles, or models are fine). You can upload between 1 and 10 images per job.

If you want to recolor toward a reference image (a colour swatch or a patterned fabric), upload it the same way and keep its file_id for the reference_image field in the next step.

The upload_url is only valid for a limited time. Upload the image immediately after receiving the response.

import requests

api_url = "https://v2.api.piktid.com"
access_token = "your_access_token"
project_name = "my-recolor-project"
image_path = "path/to/tshirt-front.jpg"

# Step 1: Get pre-signed upload URL
response = requests.post(
    api_url + "/upload",
    headers={"Authorization": "Bearer " + access_token},
    json={
        "project_name": project_name,
        "filename": "tshirt-front.jpg",
    },
).json()

upload_url = response["upload_url"]
content_type = response["content_type"]
file_id = response["file_id"]

# Step 2: Upload the image binary
with open(image_path, "rb") as f:
    requests.put(
        upload_url,
        headers={"Content-Type": content_type},
        data=f.read(),
    )

print(f"Uploaded file ID: {file_id}")
Response
{
  "upload_url": "https://s3...",     // Pre-signed PUT URL
  "download_url": "https://...",
  "project_id": "abc123...",
  "project_name": "my-recolor-project",
  "file_id": "img_001...",           // FILE_ID
  "filename": "tshirt-front.jpg",
  "content_type": "image/jpeg"
}

Starting a job

A Garment Recolor job takes N product images, recolors one garment across all of them, and produces N × num_variations outputs:

  • Every input image is recolored, so the same garment looks the same across the whole set.
  • Set num_variations (1-8) to generate multiple variations per input image.
  • You define the recolor target with a reference_image, a reference_text (colour or pattern), an instruction, or any combination. At least one is required.

Credit cost: 3 credits per output at 1024, 5 at 2048, 10 at 4096, billed per output.

import requests

api_url = "https://v2.api.piktid.com"
access_token = "your_access_token"

project_id = "abc123..."
file_ids = ["img_001...", "img_002...", "img_003..."]  # Same garment across several shots
reference_image = "img_ref..."  # Optional: an uploaded colour/pattern swatch

response = requests.post(
    api_url + "/garment-recolor",
    headers={"Authorization": "Bearer " + access_token},
    json={
        "project_id": project_id,
        "images": file_ids,
        "garment_label": "t-shirt",          # Optional: auto-detected when omitted
        "reference_image": reference_image,  # Recolor target. Or use reference_text / instruction
        "num_variations": 2,
        "model": "auto",          # Optional: see "Generation options" below
        "processing_size": 2048,  # Optional: 1024 | 2048 | 4096
        "post_process": True,     # Optional: finishing pass (on by default)
        "use_anchor": True,       # Optional: keep the set consistent (on by default)
    },
).json()

job_id = response["job_id"]
total_outputs = response["total_outputs"]
print(f"Job started: {job_id} ({total_outputs} outputs)")
Response
{
  "job_id": "job_abc123...",  // JOB_ID
  "status": "pending",
  "message": "Garment recolor job created and queued",
  "total_outputs": 6          // len(images) × num_variations
}

Recolor target

Tell the job which garment to recolor and what to recolor it to. You must provide at least one of reference_image, reference_text, or instruction. They can be combined — for example, a reference image to set the colour and an instruction to refine it.

ParameterTypeRequiredDescription
garment_labelstringnoWhich garment to recolor, e.g. "t-shirt", "jacket", "hat". Automatically detected when omitted.
reference_imagestringno*File ID of a colour/pattern reference image to recolor toward. Upload it like any other image.
reference_textstringno*A colour or pattern described as text, e.g. a hex code "#2E5A3B" or a colour name like "forest green".
instructionstringno*Free-form recolor instruction, e.g. "make it forest green".

* At least one of reference_image, reference_text, or instruction must be provided.

Output options

ParameterTypeDefaultDescription
num_variationsinteger (1-8)1Number of variations to generate per input image. Total outputs = len(images) × num_variations.
processing_size1024 | 2048 | 40962048Output resolution in pixels. Drives credit cost: 3 / 5 / 10 credits per output. Larger sizes cost more and take longer.

Generation options

ParameterTypeDefaultDescription
model"auto" | "nano_banana_2" | "nano_banana_pro" | "seedream" | "gpt_image""auto"Which engine generates outputs. auto runs the default engine (Google's Nano Banana 2) with a safety fallback if content is refused. nano_banana_2 is Google's faster image model; nano_banana_pro is Google's higher-quality image model; gpt_image is OpenAI's GPT Image model. Specifying an engine disables the fallback.
use_anchorbooleantrueKeeps the recolored garment visually consistent and cohesive across every output in the set. Only has an effect when the job produces more than one output. Defaults to on.
Request with generation options
{
  "project_id": "abc123...",
  "images": ["img_001...", "img_002..."],
  "instruction": "make it forest green",
  "model": "nano_banana_2",  
  "use_anchor": true
}

AI-generated disclosure watermark

Set add_ai_watermark to true to bake a small "AI-generated" disclosure mark into the bottom-right corner of each output. This is irreversible, as it becomes part of the clean output file.

ParameterTypeDefaultDescription
add_ai_watermarkbooleanfalseBake an "AI-generated" disclosure mark into each output (irreversible).
Request with AI watermark
{
  "project_id": "abc123...",
  "images": ["img_001...", "img_002..."],
  "instruction": "make it forest green",
  "add_ai_watermark": true
}

Post-processing

post_process is enabled by default and applies a finishing pass that keeps the recolored garment tones consistent across the whole set. Set it to false to skip that step. When enabled, the job status includes post_processing_status to track this additional step.

Request without post-processing
{
  "project_id": "abc123...",
  "images": ["img_001...", "img_002..."],
  "instruction": "make it forest green",
  "post_process": false
}

Tracking progress

Use either SSE or webhooks to receive notifications for job updates.

import json
import requests

api_url = "https://v2.api.piktid.com"
access_token = "your_access_token"
job_id = "job_abc123..."

headers = {"Authorization": "Bearer " + access_token}

with requests.get(
    api_url + "/notifications/events",
    headers=headers,
    stream=True,
    timeout=600,
) as response:
    response.raise_for_status()

    for raw_line in response.iter_lines(decode_unicode=True):
        if not raw_line or raw_line.startswith(":"):
            continue
        if not raw_line.startswith("data: "):
            continue

        notification = json.loads(raw_line[6:])
        data = notification.get("data", {})
        task_id = data.get("id_task") or data.get("job_id")
        if task_id != job_id:
            continue

        print(f"Notification: {notification['name']}")
        print(f"Data: {data}")

        if notification["name"] == "batch_edit":
            status = data.get("status")
            if status == "completed":
                print("Job completed!")
                break
            if status == "failed":
                raise RuntimeError(data.get("error_message", "Job failed"))
import hashlib
import hmac

import requests
from flask import Flask, abort, request

api_url = "https://v2.api.piktid.com"
access_token = "your_access_token"
job_id = "job_abc123..."
public_webhook_url = "https://example.com/webhooks/piktid"

setup = requests.put(
    api_url + "/webhooks",
    headers={"Authorization": "Bearer " + access_token},
    json={"url": public_webhook_url},
).json()
webhook_secret = setup["secret"]

app = Flask(__name__)


def verify_signature(secret: str, body: bytes, signature_header: str) -> bool:
    expected = "sha256=" + hmac.new(secret.encode("utf-8"), body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature_header)


@app.post("/webhooks/piktid")
def handle_piktid_webhook():
    body = request.get_data()
    signature = request.headers.get("X-Webhook-Signature", "")
    if not verify_signature(webhook_secret, body, signature):
        abort(401)

    notification = request.get_json()
    data = notification.get("data", {})
    task_id = data.get("id_task") or data.get("job_id")
    if task_id != job_id:
        return "", 204

    if notification["name"] == "batch_edit":
        status = data.get("status")
        if status == "completed":
            print("Job completed!")
        elif status == "failed":
            print(f"Error: {data.get('error_message')}")

    return "", 204


app.run(port=8000)
Response
[
  {
    "id": 12345,
    "name": "batch_edit",             // Notification type
    "timestamp": 1702819200.0,
    "data": {                         // Job-specific data
      "id_task": "job_abc123...",
      "status": "completed",
      "total_images": 6,
      "processed_images": 6
    }
  }
]

Use DELETE /notifications/{id} after processing events so they are not replayed on reconnect.

Retrieving results

Once the job is complete, retrieve the recolored images.

import requests

api_url = "https://v2.api.piktid.com"
access_token = "your_access_token"
job_id = "job_abc123..."

response = requests.get(
    api_url + f"/jobs/{job_id}/results",
    headers={"Authorization": "Bearer " + access_token},
).json()

for result in response["results"]:
    print(f"Image {result['image_index']}: {result['output']['full_size']}")
Response
{
  "job_id": "job_abc123...",
  "job_type": "garment_recolor",
  "status": "completed",
  "results": [
    {
      "image_index": 0,
      "group_index": 0,
      "output": {
        "full_size": "https://...",   // Result image URL
        "thumbnail": "https://..."
      },
      "model_used": "nano_banana_2",   // Engine that produced this output
      "status": "completed"
    },
    {
      "image_index": 1,
      "group_index": 0,
      "output": {
        "full_size": "https://...",
        "thumbnail": "https://..."
      },
      "model_used": "nano_banana_2",
      "status": "completed"
    }
  ],
  "summary": {
    // Job statistics
  }
}

Bulk download

For bulk downloads, generate a temporary download URL that packages all results into a ZIP file.

import requests

api_url = "https://v2.api.piktid.com"
access_token = "your_access_token"
job_id = "job_abc123..."

# Generate download URL
response = requests.post(
    api_url + "/download",
    headers={"Authorization": "Bearer " + access_token},
    json={"job_id": job_id},
).json()

download_url = response["download_url"]
expires = response["expires"]

print(f"Download URL: {download_url}")
print(f"Expires: {expires}")

# Download the ZIP file (no auth required for the token URL)
zip_response = requests.get(download_url)
with open("results.zip", "wb") as f:
    f.write(zip_response.content)
Response
{
  "download_url": "https://v2.api.piktid.com/download/token123...",
  "expires": "2024-12-17T11:00:00Z"  // URL expiration time
}

Checking job status

You can also check the job status directly without waiting for notifications.

import requests

api_url = "https://v2.api.piktid.com"
access_token = "your_access_token"
job_id = "job_abc123..."

response = requests.get(
    api_url + f"/jobs/{job_id}/status",
    headers={"Authorization": "Bearer " + access_token},
).json()

print(f"Status: {response['status']}")
print(f"Progress: {response['progress']}%")
print(f"Processed: {response['processed_images']}/{response['total_images']}")
Response
{
  "job_id": "job_abc123...",
  "job_type": "garment_recolor",
  "status": "processing",
  "progress": 50.0,
  "total_images": 6,
  "processed_images": 3,
  "should_post_process": true,
  "post_processing_status": null,
  "created_at": "2024-12-17T10:00:00Z",
  "updated_at": "2024-12-17T10:05:00Z"
}

Error handling

Jobs may fail due to various reasons. Check the error_message field in the job status or results.

import requests

api_url = "https://v2.api.piktid.com"
access_token = "your_access_token"
job_id = "job_abc123..."

response = requests.get(
    api_url + f"/jobs/{job_id}/results",
    headers={"Authorization": "Bearer " + access_token},
).json()

if response["status"] == "failed":
    print(f"Job failed: {response['error_message']}")
else:
    for result in response["results"]:
        if result["status"] == "failed":
            print(f"Image {result['image_index']} failed: {result['error_message']}")

Common errors at job creation

POST /garment-recolor returns the following errors before the job is queued:

HTTPMeaning
400No recolor target (provide at least one of reference_image, reference_text, instruction), unsupported model, or a missing/invalid image or reference image.
402Insufficient credits. Response body includes required_credits, in_progress_credits, user_credits.
403Output size exceeds your plan's policy, or you do not have access to the target project.
404Project not found.
429Concurrent job limit reached. Non-enterprise accounts cap at 5 active jobs across model-swap, flat-2-model, create-packshot, and garment-recolor combined. The endpoint is also rate-limited to 5 requests per minute per user.
402 Insufficient credits response
{
  "error": "Insufficient credits",
  "required_credits": 30.0,
  "in_progress_credits": 5.0,
  "user_credits": 12.0
}

On this page