Flat lay on model

Virtual try-on from flat lay images with a custom model

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 one or more flat lay / SKU images to the project. 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 flat-to-model job by providing the identity (the model), the list of uploaded file IDs (all combined as inputs), and one or more instructions. Each instruction produces one output image. See Starting a job for details.


Poll for notifications to track job progress. Use the id_task parameter with your job ID. See Polling notifications for details.

Uploading identities

Before starting a flat-to-model job, you need an identity_code. You can either use an existing identity from your gallery or upload a new one.

import requests

api_url = "https://v2.api.piktid.com"
access_token = "your_access_token"
identity_image_path = "path/to/identity.jpg"

with open(identity_image_path, "rb") as f:
    response = requests.post(
        api_url + "/identity/upload",
        headers={"Authorization": "Bearer " + access_token},
        files={"image": f},
        data={"name": "Model A"},  # Optional custom name
    ).json()

identity_code = response["identity_code"]
print(f"Identity uploaded: {identity_code}")
Response
{
  "identity_code": "id_xyz...",  // IDENTITY_CODE
  "name": "Model A",
  "face_detected": true,
  "success": true,
  // ...
}

You can list existing identities using:

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-flat-lay-project"},
).json()
project_id = response["project_id"]
project_name = response["project_name"]
Response
{
  "project_id": "abc123...",   // PROJECT_ID
  "project_name": "my-flat-lay-project"
}

Uploading images

Upload all flat lay / SKU images (e.g., shirt, pants, shoes) that will be combined for each output. You can repeat this process for each image.

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-flat-lay-project"
image_path = "path/to/sku-image.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": "sku-shirt.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-flat-lay-project",
  "file_id": "sku_001...",           // FILE_ID
  "filename": "sku-shirt.jpg",
  "content_type": "image/jpeg"
}

Starting a job

Unlike model swap (1 input = 1 output), flat-to-model combines all input images and generates outputs based on the instructions list:

  • N input SKU images + M instructions = M output images
  • Each instruction produces one output image combining all inputs
  • Instructions run in parallel
import requests

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

project_id = "abc123..."
identity_code = "id_xyz..."  # From identity upload or gallery
file_ids = ["sku_001...", "sku_002..."]  # All SKU images to combine

# Define instructions - each instruction produces one output
instructions = [
    {
        "pose": "1",
        "angle": "3",
        "background": "white studio",
        "lighting": "soft",
        "options": {
            "size": "2K",
            "ar": "9:16",
            "format": "jpg",
        },
    },
    {
        "pose": "15",
        "angle": "7",
        "background": "gym rooftop",
        "lighting": "dramatic",
        "options": {
            "size": "2K",
            "ar": "3:4",
            "format": "png",
        },
    },
]

response = requests.post(
    api_url + "/flat-2-model",
    headers={"Authorization": "Bearer " + access_token},
    json={
        "identity_code": identity_code,
        "project_id": project_id,
        "images": file_ids,
        "instructions": instructions,
        "post_process": False,  # Optional: enable post-processing
        "options": {},          # Optional: global options applied to all instructions
    },
).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": "Job created successfully",
  "total_outputs": 2          // Number of output images
}

Instruction parameters

Each instruction can contain the following parameters:

ParameterTypeDescription
posestringPose reference (e.g., "1", "15"). See pose reference.
anglestringCamera angle reference (e.g., "3", "7"). See angles.
backgroundstringBackground description (e.g., "white studio", "beach").
lightingstringLighting style (e.g., "soft", "dramatic").
optionsobjectOutput options.

Output options

The options object within each instruction can contain:

ParameterValuesDescription
size"1K", "2K", "4K"Output image resolution.
ar"1:1", "3:4", "4:3", "9:16", "16:9"Aspect ratio.
format"jpg", "png"Output file format.
seedintegerRandom seed for reproducibility.

Post-processing

Set post_process to true in the job request to enable automatic post-processing of results. The job status will include post_processing_status to track this additional step.

Request with post-processing
{
  "identity_code": "id_xyz...",
  "project_id": "abc123...",
  "images": ["sku_001...", "sku_002..."],
  "instructions": [/* ... */],
  "post_process": true
}

Polling notifications

Poll for notifications to track job progress. Use the id_task parameter with your job ID.

import time
import requests

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

# Poll for notifications (recommended: every 3-5 seconds)
for _ in range(120):  # 10 minute timeout
    response = requests.post(
        api_url + "/notifications",
        headers={"Authorization": "Bearer " + access_token},
        json={
            "id_task": job_id,
            "name_list": ["batch_edit", "image_result", "error"],
        },
    ).json()

    for notification in response:
        print(f"Notification: {notification['name']}")
        print(f"Data: {notification['data']}")
        
        if notification["name"] == "batch_edit":
            status = notification["data"].get("status")
            if status == "completed":
                print("Job completed!")
                break
            elif status == "failed":
                print(f"Error: {notification['data'].get('error_message')}")
                break
    else:
        time.sleep(5)
        continue
    break
Response
[
  {
    "id": 12345,
    "name": "batch_edit",             // Notification type
    "timestamp": 1702819200.0,
    "data": {                         // Job-specific data
      "id_task": "job_abc123...",
      "status": "completed",
      "total_images": 2,
      "processed_images": 2
    }
  }
]

Retrieving results

Once the job is complete, retrieve the processed 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": "flat_2_model",
  "status": "completed",
  "results": [
    {
      "image_index": 0,
      "group_index": 0,
      "output": {
        "full_size": "https://...",   // Result image URL
        "thumbnail": "https://..."
      },
      "status": "completed"
    },
    {
      "image_index": 1,
      "group_index": 0,
      "output": {
        "full_size": "https://...",
        "thumbnail": "https://..."
      },
      "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": "flat_2_model",
  "status": "processing",
  "progress": 50.0,
  "total_images": 2,
  "processed_images": 1,
  "should_post_process": false,
  "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']}")