Adobe Photoshop API v1 to v2 migration - LLM reference guide

Purpose: This document serves as a comprehensive reference for AI assistants (LLMs) helping developers migrate from Adobe Photoshop API v1 to v2. It consolidates patterns, examples, and troubleshooting guidance from all migration guides.

Table of contents

  1. Quick reference and decision trees
  2. Core architectural changes
  3. Migration patterns by operation type
  4. Storage solutions reference
  5. Output types migration
  6. ICC profile support (V2 new feature)
  7. Common migration issues and solutions
  8. Code transformation examples
  9. Validation checklist

Quick reference and decision trees

Complete V1 to V2 endpoint mapping

V1 Endpoint
V2 Endpoint
Category
Description
/lrService/autoTone
/v2/edit
Edit Operations
Auto tone adjustment
/lrService/autoStraighten
/v2/edit
Edit Operations
Auto straighten image
/lrService/presets
/v2/edit
Edit Operations
Apply Lightroom presets
/lrService/xmp
/v2/edit
Edit Operations
Apply XMP metadata
/lrService/edit
/v2/edit
Edit Operations
Manual adjustments
/pie/psdService/renditionCreate
/v2/create-composite
Format Conversion
Convert PSD to other formats
/pie/psdService/documentCreate
/v2/create-composite
Document Creation
Create new blank documents
/pie/psdService/documentOperations
/v2/create-composite
Document/Layer Operations
Document-level operations and layer manipulation
/pie/psdService/smartObject
/v2/create-composite
Document/Layer Operations
Dedicated smart object place/replace endpoint
/pie/psdService/photoshopActions
/v2/execute-actions
Actions
Execute .atn action files
/pie/psdService/actionJSON
/v2/execute-actions
Actions
Execute inline ActionJSON
/pie/psdService/text
/v2/execute-actions
Actions
Dedicated text layer edit endpoint
/pie/psdService/productCrop
/v2/execute-actions
Actions
Product crop convenience API
/pie/psdService/depthBlur
Not yet supported (Neural Filters unavailable)
Actions
Depth blur convenience API
/pie/psdService/splitView
/v2/execute-actions
Actions
Split view convenience API
/pie/psdService/sideBySide
/v2/execute-actions
Actions
Side by side convenience API
/pie/psdService/artboardCreate
/v2/create-artboard
Artboards
Create artboards from images
/pie/psdService/documentManifest
/v2/generate-manifest
Manifest
Generate PSD manifest
/pie/psdService/status/{jobId}
/v2/status/{jobId}
Status
Check job status (Photoshop)
/lrService/status/{jobId}
/v2/status/{jobId}
Status
Check job status (Lightroom)
Sensei PSDC Engine
/v2/create-composite
Format Conversion
PSD/Cloud PSD conversion engine (engine retiring, not endpoint)

Decision tree for migration path selection

START: What are you trying to do?
│
├─ Apply image adjustments (autoTone, exposure, etc.)?
│  └─> Use /v2/edit endpoint
│     └─> Guide: Edit Operations Migration
│
├─ Convert PSD to JPEG/PNG/TIFF without edits?
│  └─> Use /v2/create-composite (no edits block)
│     └─> Guide: Format Conversion Migration
│
├─ Using sensei PSDC engine for PSD/Cloud PSD conversion?
│  └─> Use /v2/create-composite
│     └─> URNs map to creativeCloudFileId, paths to creativeCloudPath
│     └─> Guide: Format Conversion Migration (PSDC Engine section)
│
├─ Create a new blank document?
│  └─> Use /v2/create-composite with document params
│     └─> Guide: Document Creation Migration
│
├─ Resize/crop/trim existing document?
│  └─> Use /v2/create-composite with edits.document
│     └─> Guide: Document Operations Migration
│
├─ Add/edit/delete layers in a PSD?
│  └─> Use /v2/create-composite with edits.layers
│     ├─> Image layers? → Layer Operations: Image
│     ├─> Text layers? → Layer Operations: Text
│     ├─> Adjustment layers? → Layer Operations: Adjustments
│     ├─> Smart objects (via V1 documentOperations)? → Layer Operations: Smart Objects
│     ├─> Smart objects (via V1 /psdService/smartObject)? → convenience-apis/smart-object-replace
│     └─> Move/delete/masks? → Layer Operations: Advanced
│
├─ Execute Photoshop actions or scripts?
│  └─> Use /v2/execute-actions
│     └─> Guide: Actions Migration
│
├─ Edit text layers (V1 /psdService/text endpoint)?
│  └─> Use /v2/execute-actions with ActionJSON or UXP
│     └─> Guide: Text Endpoint Migration Specific (validation checklist section)
│
├─ Create artboards from multiple images?
│  └─> Use /v2/create-artboard
│     └─> Guide: Artboard Migration
│
├─ Get PSD structure and layer information?
│  └─> Use /v2/generate-manifest
│     └─> Guide: Manifest Migration
│
└─ Check job status?
   └─> Use /v2/status/{jobId}
      └─> Guide: Status Migration

Migration path by V1 endpoint

If using Lightroom endpoints:

If using PSD layer operations:

If using dedicated smart object or text endpoints:

If using action endpoints:

If using format conversion:

For a complete behavioral reference:

Core architectural changes

Breaking changes summary

1. Base URL changes

Environment
V1 Base URL
V2 Base URL
Production
https://image.adobe.io
https://photoshop-api.adobe.io

Action Required: Update all base URL references in your code.

2. Request structure changes

V1 Pattern:

{
  "inputs": [
    {
      "href": "<URL>",
      "storage": "external"
    }
  ],
  "options": { ... },
  "outputs": [
    {
      "href": "<URL>",
      "storage": "external",
      "type": "image/jpeg"
    }
  ]
}

V2 Pattern:

{
  "image": {
    "source": {
      "url": "<URL>"
    }
  },
  "edits": { ... },
  "outputs": [
    {
      "destination": {
        "url": "<URL>"
      },
      "mediaType": "image/jpeg"
    }
  ]
}

Key Changes:

data-variant=info
data-slots=text
Artboard exception: The /v2/create-artboard endpoint uses images (plural) with a source object per image, not image (singular), because it accepts multiple input images.

3. Storage type specification

V1: Required for all storage types

{
  "storage": "external"
}

V2: Optional for external presigned URLs, required only for Azure and Dropbox

{
  "destination": {
    "url": "<URL>",
    "storageType": "azure"  // Only for Azure/Dropbox
  }
}

When to use storageType in V2:

4. Authentication (unchanged)

V2 uses the same OAuth Server-to-Server authentication as V1:

curl -X POST 'https://ims-na1.adobelogin.com/ims/token/v3' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=client_credentials' \
  -d "client_id=$CLIENT_ID" \
  -d "client_secret=$CLIENT_SECRET" \
  -d 'scope=openid,AdobeID,read_organizations'

New capabilities in V2

1. Batch operations

V1 Required Multiple Sequential Calls:

# Call 1
POST /lrService/autoTone

# Call 2
POST /lrService/autoStraighten

# Call 3
POST /lrService/edit

V2 Single Call:

{
  "edits": {
    "autoTone": true,
    "autoStraighten": {"enabled": true},
    "light": {"exposure": 0.5}
  }
}

2. Four storage options

  1. External Storage - Your presigned URLs (AWS S3, Azure, etc.)
  2. Creative Cloud Storage - Direct CC integration
  3. Embedded Storage - Inline response (text outputs only)
  4. Hosted Storage - Adobe-managed temporary storage

3. Published action files

V1 convenience APIs (productCrop, depthBlur, etc.) used hidden server-side actions. V2 publishes these ActionJSON definitions so you can:

4. Enhanced layer operations

V2 supports comprehensive layer manipulation:

5. Better error handling

{
  "status": "failed",
  "errorDetails": [
    {
      "errorCode": "400401",
      "message": "The value provided is not valid."
    },
    {
      "errorCode": "400420",
      "message": "Required fields missing on the API request."
    }
  ]
}

Migration patterns by operation type

A. Edit operations (Lightroom)

V1 Endpoints Consolidated:

V2 Endpoint: /v2/edit

Pattern: Multiple sequential V1 calls → Single V2 call

V1 Approach (3 separate calls):

# Step 1
curl -X POST https://image.adobe.io/lrService/autoTone \
  -d '{"inputs": {"href": "<URL>", "storage": "external"}}'

# Step 2
curl -X POST https://image.adobe.io/lrService/autoStraighten \
  -d '{"inputs": {"href": "<URL>", "storage": "external"}}'

# Step 3
curl -X POST https://image.adobe.io/lrService/edit \
  -d '{"inputs": {"href": "<URL>"}, "options": {"Exposure": 1.2}}'

V2 Approach (single call):

curl -X POST https://photoshop-api.adobe.io/v2/edit \
  -H "Authorization: Bearer $token" \
  -H "x-api-key: $apiKey" \
  -H "Content-Type: application/json" \
  -d '{
  "image": {
    "source": {
      "url": "<SIGNED_GET_URL>"
    }
  },
  "edits": {
    "autoTone": true,
    "autoStraighten": {
      "enabled": true,
      "constrainCrop": true
    },
    "light": {
      "exposure": 1.2,
      "contrast": 10
    },
    "color": {
      "saturation": 15
    }
  },
  "outputs": [
    {
      "destination": {
        "url": "<SIGNED_POST_URL>"
      },
      "mediaType": "image/jpeg"
    }
  ]
}'

Adjustment Categories:

Parameter Name Changes:

XMP with masks migration (localized adjustments)

For advanced Camera Raw editing workflows with localized adjustments, masks can be included alongside XMP metadata. These masks are binary files that define selection areas for localized adjustments and are passed as part of the /lrService/xmp endpoint in V1.

V1 Approach (/lrService/xmp):

{
  "inputs": {
    "source": {
      "href": "<IMAGE_URL>",
      "storage": "external"
    }
  },
  "options": {
    "xmp": "<XMP_STRING>",
    "masks": [
      {
        "href": "<MASK_FILE_URL>",
        "storage": "external",
        "digest": "37B3568A954F0C80CA946DA0187FB9E2"
      }
    ]
  },
  "outputs": [...]
}

Note: In V1, masks were provided in options.masks alongside the XMP string at the /lrService/xmp endpoint.

V2 Approach:

{
  "image": {
    "source": {
      "url": "<IMAGE_URL>"
    }
  },
  "edits": {
    "xmp": {
      "source": {
        "url": "<XMP_URL>"
      },
      "masks": [
        {
          "source": {
            "url": "<MASK_FILE_URL>"
          },
          "digest": "37B3568A954F0C80CA946DA0187FB9E2"
        }
      ]
    }
  },
  "outputs": [...]
}

Key Changes:

  1. Endpoint Change: /lrService/xmp/v2/edit
  2. Structure Change: options.masks (sibling to xmp) → edits.xmp.masks (nested inside xmp)
  3. Mask Source: href + storagesource.url
  4. XMP Format: V1 accepted inline XMP string in options.xmp, V2 requires file reference in edits.xmp.source
  5. Digest: Remains the same - fingerprint for mask identification
  6. Storage Parameter: No longer required for standard URLs (only for Azure/Dropbox)

Multiple Masks Example:

{
  "edits": {
    "xmp": {
      "source": {
        "url": "<XMP_URL>"
      },
      "masks": [
        {
          "source": {
            "url": "<MASK_FILE_1_URL>"
          },
          "digest": "37B3568A954F0C80CA946DA0187FB9E2"
        },
        {
          "source": {
            "url": "<MASK_FILE_2_URL>"
          },
          "digest": "D15158972C7044869B171697AEBAFB0F"
        }
      ]
    }
  }
}

Important Notes:

Note on /lrService/mask endpoint: The V1 /lrService/mask endpoint is a separate, unrelated feature for AI-based mask generation (selectSubject, selectSky, etc.) and has a different deprecation path. The masks discussed in this section are for localized Camera Raw adjustments passed via the /lrService/xmp endpoint.

XMP with orientation override

V2 allows you to override the image's embedded orientation metadata by specifying an orientation parameter within the xmp object. This is useful for applying rotation or flip transformations as part of your Camera Raw workflow.

V2 Structure:

{
  "edits": {
    "xmp": {
      "source": {
        "url": "<XMP_URL>"
      },
      "orientation": "right_top"
    }
  }
}

Orientation Values (EXIF/TIFF Specification):

Value
Description
EXIF Code
Common Use
"top_left"
Column 0 = left, Row 0 = top
1
Normal orientation
"top_right"
Column 0 = right, Row 0 = top
2
Horizontal flip
"bottom_right"
Column 0 = right, Row 0 = bottom
3
180° rotation
"bottom_left"
Column 0 = left, Row 0 = bottom
4
Vertical flip
"left_top"
Column 0 = top, Row 0 = left
5
90° CCW + H-flip
"right_top"
Column 0 = top, Row 0 = right
6
90° CW rotation
"right_bottom"
Column 0 = bottom, Row 0 = right
7
90° CCW rotation
"left_bottom"
Column 0 = bottom, Row 0 = left
8
90° CW + H-flip

Combined with Masks Example:

{
  "edits": {
    "xmp": {
      "source": {
        "url": "<XMP_URL>"
      },
      "orientation": "right_top",
      "masks": [
        {
          "source": {
            "url": "<MASK_FILE_URL>"
          },
          "digest": "37B3568A954F0C80CA946DA0187FB9E2"
        }
      ]
    }
  }
}

Key Points:

B. Composite operations (document/layer)

V1 Endpoints Consolidated:

V2 Endpoint: /v2/create-composite

Use Cases:

  1. Format Conversion - No edits block, just convert PSD to JPEG/PNG
  2. Document Creation - image with dimensions, no source
  3. Document Operations - edits.document with resize/crop/trim
  4. Layer Operations - edits.layers with add/move/delete/edit

Request Structure Pattern:

{
  "image": {
    "source": {"url": "<URL>"}  // For existing documents
    // OR
    "width": 1920,                // For new documents
    "height": 1080,
    "resolution": {"unit": "density_unit", "value": 72},  // Object format required
    "fill": "white",  // Also: "transparent", "background_color" (NOT "backgroundColor" — camelCase is rejected in V2)
                      // Or object: {"solidColor": {"red": 255, "green": 255, "blue": 255}}
    "mode": "rgb",    // Use "grayscale" (not "gray") for grayscale documents
    "depth": 8        // Integer, not string. Valid values depend on mode:
                      // bitmap: 1; grayscale/rgb/hsb: 8, 16, 32; cmyk/lab/multichannel: 8, 16; indexed/duotone: 8
  },
  "edits": {
    "document": {
      "resize": {...},
      "crop": {...},
      "trim": {...}
    },
    "layers": [
      {
        "type": "layer|text_layer|adjustment_layer|smart_object_layer|solid_color_layer",
        "name": "Layer Name",
        "operation": {
          "type": "add|edit|delete",
          "placement": {...}
        },
        // Layer-specific properties
      }
    ]
  },
  "outputs": [...]
}

Layer Operation Types:

data-variant=info
data-slots=text
Layer lock: Use "protection": ["all"] to lock, "protection": ["none"] to unlock (replaces V1 "locked": true/false). When using all or none, the array must contain only that element. Individual flags: transparency, composite, position, artboard_autonest. Clipping masks: use top-level "isClipped": true on a layer (not mask.clip).

Layer Types:

Placement Options:

{
  "placement": {
    "type": "top"  // or "bottom", "above", "below", "into"
    // For relative placement:
    "referenceLayer": {
      "name": "Layer Name"  // or "id": 123
    }
  }
}
data-variant=warning
data-slots=text
Layer processing order (breaking change): V1 processed layers bottom-up (last item in array first); V2 processes layers top-down (first item in array first). If a layer uses above, below, or into placement with a referenceLayer, the referenced layer must appear earlier in the array (already created/existing) before the layer that references it.

Layer Property Locations (V2):

Property
V1
V2 (most layer types)
V2 (smart_object_layer)
Opacity
blendOptions.opacity
top-level opacity
blendOptions.opacity
Blend mode
blendOptions.blendMode
top-level blendMode
blendOptions.blendMode
Visibility
visible
isVisible
isVisible
Lock
locked: true/false
protection: ["all"] / ["none"]
protection: ["all"] / ["none"]
data-variant=info
data-slots=text
For smart_object_layer, opacity and blendMode remain nested under blendOptions. For all other layer types (image, text, adjustment, solid color, group, background), they are top-level properties in V2.

Layer Transforms (V1 → V2):

Layer Alignment (V1 → V2):

Smart Object Specifics:

Adjustment Layer Specifics:

Text Layer Specifics:

Background Layer Specifics:

Allowed direct edits (no conversion needed):

Restricted fields — return error when set directly on background_layer: transform, transformMode, pixelMask, protection, blendOptions

Mutually exclusive within a single entry: image (pixel replacement) and convertToLayer: true cannot appear in the same layer entry. Use two separate entries in the layers array (see chained workflow below).

Convert to regular layer: Use convertToLayer: true to promote the background to a regular layer. Use convertedLayerName to assign a name immediately — subsequent layers entries in the same API call can then target it by that name:

{
  "type": "background_layer",
  "name": "Background",
  "operation": { "type": "edit" },
  "convertToLayer": true,
  "convertedLayerName": "Converted Background"
}

After conversion the layer appears in the manifest with type: "layer" and the assigned name. All previously-restricted operations (transform, protection, blendOptions, etc.) are now valid.

Chained workflow — pixel replace + convert + transform in one API call:

Place multiple entries in the layers array; the processor applies them sequentially:

"layers": [
  {
    "type": "background_layer",
    "name": "Background",
    "operation": { "type": "edit" },
    "image": { "source": { "url": "<NEW_BACKGROUND_IMAGE>" } }
  },
  {
    "type": "background_layer",
    "name": "Background",
    "operation": { "type": "edit" },
    "convertToLayer": true,
    "convertedLayerName": "Converted Background"
  },
  {
    "type": "layer",
    "name": "Converted Background",
    "operation": { "type": "edit" },
    "transformMode": "custom",
    "transform": { "angle": 15.0, "skew": { "horizontal": 0.0, "vertical": 0.0 } }
  }
]

Entry 1 replaces the background pixels. Entry 2 converts the background to a named regular layer. Entry 3 applies a transform to the converted layer — using name: "Converted Background" which was assigned in entry 2.

V1 → V2 background layer migration checklist:

  • Change type: "backgroundLayer"type: "background_layer"
  • Change id: -1 → use id from manifest (or name: "Background")
  • Change visibleisVisible
  • Remove locked / protection from background_layer entries (add after conversion)
  • Replace add: {}operation: { "type": "add" } (no placement)
  • Replace edit: {}operation: { "type": "edit" }
  • Replace input.hrefimage.source.url
  • To apply transform, protection, or blendOptions: add convertToLayer: true + convertedLayerName, then target the converted layer by name in a subsequent entry

C. Actions migration

V1 Endpoints Consolidated:

V2 Endpoint: /v2/execute-actions

Key Change: Action files for convenience APIs are now published!

Pattern for .atn Files:

V1:

{
  "options": {
    "actions": [
      {"href": "<ACTION_URL>", "storage": "external"}
    ]
  }
}

V2:

{
  "options": {
    "actions": [
      {
        "source": {
          "url": "<ACTION_URL>"
        }
      }
    ]
  }
}

Pattern for Inline ActionJSON:

V1:

{
  "options": {
    "actionJSON": [
      {"_obj": "convertMode", "to": {...}}
    ]
  }
}

V2:

{
  "options": {
    "actions": [
      {
        "source": {
          "content": "[{\"_obj\":\"convertMode\",\"to\":{...}}]",
          "contentType": "application/json"
        }
      }
    ]
  }
}

Important: ActionJSON must be stringified JSON in V2.

Multiple Actions: V2 supports up to 10 actions executed in sequence:

{
  "options": {
    "actions": [
      {"source": {"url": "<ACTION_1_URL>"}},
      {"source": {"url": "<ACTION_2_URL>"}},
      {"source": {"url": "<ACTION_3_URL>"}}
    ]
  }
}

Additional Resources:

{
  "options": {
    "actions": [...],
    "additionalContents": [  // Max 25 (images/compositing resources)
      {"source": {"url": "<IMAGE_URL>"}}
    ],
    "brushes": [           // Max 10 — must use external URLs (binary, no inline)
      {"source": {"url": "<BRUSH_URL>"}}
    ],
    "patterns": [          // Max 10 — must use external URLs (binary, no inline)
      {"source": {"url": "<PATTERN_URL>"}}
    ],
    "fontOptions": {
      "additionalFonts": [ // Max 10 — must use external URLs (binary, no inline)
        {"source": {"url": "<FONT_URL>"}}
      ],
      "missingFontStrategy": "use_default"
    }
  }
}

Additional Contents Placeholder: Reference in ActionJSON or UXP scripts as __ADDITIONAL_CONTENT_0__, __ADDITIONAL_CONTENT_1__, etc. (0-based index matches position in additionalContents array). Binary resources (brushes, patterns, fonts) must use external URLs — inline content is not supported for these types.

Convenience API details

Product Crop:

Depth Blur: Depth Blur is not yet supported in V2. Neural Filters are unavailable in V2; continue using the V1 /pie/psdService/depthBlur endpoint for this feature. The parameters below are for reference only.

Text Layer Operations:

When to use ActionJSON vs UXP for text edits:

Use Case
Use
Fixed edits (font, size, color) for known layer names
ActionJSON (options.actions)
Conditional edits (only if layer is text type, only if name matches)
UXP (options.uxp)
Iterate all layers and apply logic
UXP
Toggle or state-based changes (e.g. bold → regular)
UXP

ActionJSON pattern (stringified, passed as options.actions[].source.content with contentType: "application/json"):

  1. select layer by _namemakeVisible: true
  2. set textStyle: font size (size with _unit: "pointsUnit", textOverrideFeatureName: 808465458, typeStyleOperationType: 3)
  3. set textStyle: font (fontPostScriptName, fontName, fontStyleName)
  4. set textStyle: color (color with _obj: "RGBColor", red, green, blue)

For multiple layers: repeat the select + set sequence within the same stringified ActionJSON array.

UXP pattern (passed as options.uxp.source.content with contentType: "application/javascript"):

Split View:

Side by Side:

UXP Scripts (Limited Availability):

{
  "options": {
    "uxp": {
      "source": {
        "content": "const app = require('photoshop').app; app.activeDocument.flatten();",
        "contentType": "application/javascript"
      }
    }
  }
}

Use plugin-temp:/filename.ext path in UXP scripts to write output files to the plugin temporary directory (e.g., plugin-temp:/result.json). Then reference them in scriptOutputPattern.

Script Output Discovery:

{
  "outputs": [
    {
      "destination": {"validityPeriod": 3600},
      "mediaType": "application/json",
      "scriptOutputPattern": "result.json"
    }
  ]
}

Supports glob patterns: *.json, result-*.png, etc.

D. Other operations

Artboards:

Manifest:

Status:

E. Export layers (single vs multi-layer)

Export layers via outputs[].layers on the /v2/create-composite endpoint. Behavior differs between single-layer and multi-layer exports.

Single-Layer Export — specify exactly one layer in outputs[].layers:

Multi-Layer Export — specify two or more layers in outputs[].layers:

Document Export — no layers specified (exports full document):

// Single-layer: V2 example with cropMode
{
  "image": {"source": {"url": "<PSD_URL>"}},
  "outputs": [{
    "destination": {"url": "<OUTPUT_URL>"},
    "mediaType": "image/png",
    "layers": [{"id": 1096}],
    "cropMode": "trim_to_transparency"
  }]
}

// Multi-layer: V2 example with cropMode (no PSD)
{
  "image": {"source": {"url": "<PSD_URL>"}},
  "outputs": [{
    "destination": {"url": "<OUTPUT_URL>"},
    "mediaType": "image/jpeg",
    "layers": [{"id": 1096}, {"id": 996}],
    "quality": "maximum",
    "cropMode": "trim_to_transparency"
  }]
}

Layer identifier: mutually exclusive. Each entry in outputs[].layers[] must use either id or name — not both. V1 tolerated both fields together as disambiguation hints; V2 rejects the payload with: "Each layer reference must contain exactly one of id or name (not both)." Prefer id when present; drop name.

V1 outputs[].layers[] with visible field: Some V1 renditionCreate/documentOperations payloads included outputs[].layers[] entries with {id, visible: true} to select which layers appear in the composite export. Map these to V2 outputs[].layers[] as [{id: N}, ...] — this triggers multi-layer composite export. Do not move them to edits.layers[] (that path requires type + operation and performs layer edits, not export selection).

Storage solutions reference

Storage decision matrix

Use Case
Recommended Storage
Why
Production workflows
External presigned URLs
Full control, scalability, integration
Creative Cloud/ACP integration
Creative Cloud paths
Seamless CC workflow via ACP, direct access
Large files or batches
External storage
Better performance, cost-effective
XMP metadata / JSON manifests
Embedded (string/json)
Immediate response, no storage setup
Temporary edits
Hosted (presigned URLs)
No storage management required

1. External storage (presigned URLs)

Supports: AWS S3, Azure Blob, Dropbox, Frame.io, Google Drive

For Input:

{
  "image": {
    "source": {
      "url": "<PRESIGNED_GET_URL>"
    }
  }
}

For Output:

{
  "outputs": [
    {
      "destination": {
        "url": "<PRESIGNED_PUT_URL>",
        "storageType": "azure"  // Only for Azure or Dropbox
      },
      "mediaType": "image/jpeg"
    }
  ]
}

When to use storageType:

2. Creative Cloud storage

Note: Creative Cloud Storage refers specifically to ACP (Adobe Content Platform) for file storage and access.

{
  "image": {
    "source": {
      "creativeCloudPath": "my-folder/input.psd"
      // OR
      "creativeCloudFileId": "urn:aaid:...",
      "creativeCloudProjectId": "urn:aaid:sc:..."  // Optional
    }
  },
  "outputs": [
    {
      "destination": {
        "creativeCloudPath": "my-folder/output.jpg"
      },
      "mediaType": "image/jpeg"
    }
  ]
}
data-variant=warning
data-slots=text
No leading slash: creativeCloudPath values must NOT have a leading /. Use "my-folder/file.psd", not "/my-folder/file.psd".

When to use:

3. Embedded storage

For text-based outputs only (XMP, JSON):

{
  "outputs": [
    {
      "destination": {
        "embedded": "string"  // For XMP (XML text)
        // OR
        "embedded": "json"    // For JSON manifests
        // OR
        "embedded": "base64"  // For small binary data
      },
      "mediaType": "application/rdf+xml"  // or application/json
    }
  ]
}

When to use:

IMPORTANT: Do NOT use for images. Only for text/metadata outputs.

Response format:

{
  "result": {
    "outputs": [
      {
        "destination": {
          "embedded": "string",
          "content": "<?xml version=\"1.0\"...>"  // For string
          // OR for JSON:
          // "embedded": "json",
          // "content": {"document": {...}}
        },
        "mediaType": "application/rdf+xml"
      }
    ]
  }
}

4. Hosted storage

Adobe-managed temporary storage:

{
  "outputs": [
    {
      "destination": {
        "validityPeriod": 3600  // Seconds: 60-86400 (max 1 day)
      },
      "mediaType": "image/jpeg"
    }
  ]
}

When to use:

Response:

{
  "result": {
    "outputs": [
      {
        "destination": {
          "url": "https://adobe-hosted-storage.../file.jpg?expires=..."
        },
        "mediaType": "image/jpeg"
      }
    ]
  }
}

IMPORTANT: Download before URL expires (maximum 24 hours from job completion)!

Output types migration

Overview

V2 introduces significant changes to output format specifications affecting all endpoints that generate outputs. This section covers the structural changes and format-specific parameter transformations for JPEG, PNG, PSD, and TIFF outputs.

Structural changes (all output formats)

All output types share these common structural changes:

V1 Field
V2 Field
Required
Notes
outputs[].href
outputs[].destination.url
Yes
Nested in destination object
outputs[].type
outputs[].mediaType
Yes
Renamed for clarity
outputs[].storage
outputs[].destination.storageType
No*
Only for Azure/Dropbox

*storageType is optional and only required for Azure Blob Storage and Dropbox.

V1 Pattern:

{
  "outputs": [{
    "href": "https://...",
    "storage": "external",
    "type": "image/jpeg"
  }]
}

V2 Pattern:

{
  "outputs": [{
    "destination": {
      "url": "https://..."
    },
    "mediaType": "image/jpeg"
  }]
}

JPEG quality migration

V1: Numeric values (1-7 or 1-12) V2: Descriptive string enums

JPEG quality mapping

V1 Numeric
V2 String
Use Case
1-2
"low"
Thumbnails, small previews
3-4
"medium"
Standard web images
5-6
"high"
High-quality web images
7
"maximum"
Print, production
N/A
"photoshop_max"
Professional print, highest quality (recommended default)
N/A
"very_poor"
Smallest file size (V2 only)
N/A
"poor"
Very small files (V2 only)

Default: Use "photoshop_max" for production workflows.

V1 implicit quality: When a V1 execute-actions or actionJSON payload omits the quality field entirely, V1 uses its highest-quality JPEG encoder setting. Always set quality: "photoshop_max" in V2 when migrating from a V1 payload with no quality field — omitting it in V2 may default to a lower level and produce pixel-level differences against V1 reference outputs.

execute-actions pixel fidelity: V1 and V2 use different JPEG encoders. Even with identical quality settings, sub-1% pixel differences are expected. Use "photoshop_max" as the baseline when pixel fidelity is critical; accept sub-1% diffs as normal for JPEG across API versions.

V1 Example:

{
  "type": "image/jpeg",
  "quality": 7,
  "width": 2000
}

V2 Example:

{
  "mediaType": "image/jpeg",
  "quality": "maximum",
  "width": 2000
}

PNG compression migration

V1: Three compression levels: "small", "medium", "large" V2: Ten granular compression levels (zlib/libpng standard)

PNG compression mapping

V1
V2 Recommended
V2 Alternatives
Notes
"small"
"maximum"
"very_high", "high"
Smallest files, slower
"medium"
"medium"
"default", "medium_high", "medium_low"
Balanced (default)
"large"
"low"
"very_low", "none"
Fastest, larger files

V2 PNG compression values (complete)

V1 Example:

{
  "type": "image/png",
  "compression": "medium"
}

V2 Example:

{
  "mediaType": "image/png",
  "compression": "medium"
}

Important: PNG uses compression, NOT quality.

PSD and TIFF outputs

PSD and TIFF outputs have no format-specific parameters (no quality or compression options in V2). Only the structural field changes apply:

V1 PSD:

{
  "href": "https://...",
  "storage": "external",
  "type": "image/vnd.adobe.photoshop"
}

V2 PSD:

{
  "destination": {
    "url": "https://..."
  },
  "mediaType": "image/vnd.adobe.photoshop"
}

Cloud PSD (PSDC): Use "mediaType": "document/vnd.adobe.cpsd+dcxucf" (primary form). Both document/vnd.adobe.cpsd+dcxucf and document/vnd.adobe.cpsd+dcx are supported for ACP (Adobe Cloud Platform) storage and external storage. The older V1 form image/vnd.adobe.photoshop+dcx is not used in V2.

Common output fields

All output types support these optional fields:

Multiple outputs

V2 supports up to 25 outputs per request:

{
  "outputs": [
    {
      "destination": {"url": "..."},
      "mediaType": "image/jpeg",
      "quality": "maximum",
      "width": 2400
    },
    {
      "destination": {"url": "..."},
      "mediaType": "image/jpeg",
      "quality": "medium",
      "width": 800
    },
    {
      "destination": {"url": "..."},
      "mediaType": "image/png",
      "compression": "high"
    }
  ]
}

Color mode and bit depth validation

The API automatically converts source images when the color mode or bit depth is not supported by the target format. The behavior differs by export path.

data-variant=info
data-slots=text
Export paths: Full document = whole document export. Single layer = exporting one layer. Layers composite = exporting a composite of multiple selected layers. Layers composite does not apply any format-specific conversion.

Bit depth validation table

Export Path
Target Format
Supported Depths
Fallback
ICC Profile Specified
Full document
JPEG
8-bit only
Convert to 8-bit if needed
No
Full document
JPEG
8-bit only
No conversion
Yes
Full document
PNG
8-bit, 16-bit
Convert to 8-bit if needed
No
Full document
PNG
8-bit, 16-bit
No conversion
Yes
Full document
TIFF
8, 16, 32-bit
No conversion
N/A
Full document
PSD
8, 16, 32-bit
No conversion
N/A
Full document
PSDC
8, 16, 32-bit
No conversion
N/A
Single layer
JPEG
8-bit only
Convert to 8-bit if needed
N/A
Single layer
PNG
8-bit, 16-bit
Convert to 8-bit if needed
N/A
Single layer
TIFF
8, 16, 32-bit
No conversion
N/A
Single layer
PSD
8, 16-bit only
Convert to 8-bit if needed
N/A
Layers composite
JPEG
No conversion
N/A
Layers composite
PNG
No conversion
N/A
Layers composite
TIFF
No conversion
N/A
data-variant=info
data-slots=text
Single-layer PSD export supports only 8 or 16-bit (not 32-bit).

Color mode validation table

Export Path
Target Format
Supported Modes
Fallback
ICC Profile Specified
Full document
JPEG
RGB, Grayscale, CMYK
Convert to RGB if needed
No
Full document
JPEG
RGB, Grayscale, CMYK
No conversion
Yes
Full document
PNG
RGB, Grayscale
Convert to RGB if needed
No
Full document
PNG
RGB, Grayscale
No conversion
Yes
Full document
TIFF
All
No conversion
N/A
Full document
PSD
All
No conversion
N/A
Full document
PSDC
All
No conversion
N/A
Single layer
JPEG
RGB, Grayscale, CMYK
Convert to RGB if needed
N/A
Single layer
PNG
RGB, Grayscale
Convert to RGB if needed
N/A
Single layer
TIFF
All
No conversion
N/A
Single layer
PSD
RGB, Grayscale, CMYK
Convert to RGB if needed
N/A
Layers composite
JPEG
No conversion
N/A
Layers composite
PNG
No conversion
N/A
Layers composite
TIFF
No conversion
N/A

Output type-specific issues

Issue: Numeric JPEG quality

Problem:

{
  "mediaType": "image/jpeg",
  "quality": 7
}

Error:

Invalid value '7' at path 'outputs[0].quality'.
Accepted values are: very_poor, poor, low, medium, high, maximum, photoshop_max

Solution:

{
  "mediaType": "image/jpeg",
  "quality": "maximum"
}

Issue: Old PNG compression values

Problem:

{
  "mediaType": "image/png",
  "compression": "small"
}

Error:

Invalid value 'small' at path 'outputs[0].compression'.

Solution:

{
  "mediaType": "image/png",
  "compression": "maximum"
}

Issue: Using quality for PNG

Problem:

{
  "mediaType": "image/png",
  "quality": "high"
}

Error:

'quality' parameter is not supported for PNG format

Solution:

{
  "mediaType": "image/png",
  "compression": "high"
}

ICC profile support (V2 new feature)

ICC profile support is a new V2 capability — V1 had no output color profile configuration.

Overview

ICC profiles can be applied in two places:

  1. On outputs — controls the exported file's color space. Add iccProfile to any output in /v2/create-composite, /v2/create-artboard, or /v2/execute-actions. Supported for JPEG, PNG, TIFF, and PSD — not for PSDC (Cloud PSD).
  2. On image (document creation) — sets the document's embedded color profile at creation time. Add iccProfile to the image block when creating a new document (no image.source). Supports both standard and custom profiles.

Standard profiles

Use a predefined profile name (no external file needed):

"iccProfile": {
  "type": "standard",
  "name": "sRGB IEC61966-2.1",
  "imageMode": "rgb"
}

RGB profiles: "Adobe RGB (1998)", "Apple RGB", "ColorMatch RGB", "sRGB IEC61966-2.1"

Grayscale profiles (Dot Gain): "Dot Gain 10%", "Dot Gain 15%", "Dot Gain 20%", "Dot Gain 25%", "Dot Gain 30%"

Grayscale profiles (Gray Gamma): "Gray Gamma 1.8", "Gray Gamma 2.2"

data-variant=warning
data-slots=text
Profile names must match exactly (including spaces and punctuation). imageMode for standard profiles: "rgb" or "grayscale" only — CMYK is not supported.

Custom profiles (enables CMYK)

Use your own .icc file — required for CMYK output:

"iccProfile": {
  "type": "custom",
  "name": "U.S. Web Coated (SWOP) v2",
  "imageMode": "cmyk",
  "source": {
    "url": "https://example.com/profiles/custom.icc"
  }
}

imageMode options for custom: "rgb", "grayscale", or "cmyk".

Format support table

Format
mediaType
ICC Profile Supported
JPEG
image/jpeg
Yes
PNG
image/png
Yes
TIFF
image/tiff
Yes
PSD
image/vnd.adobe.photoshop
Yes
PSDC
document/vnd.adobe.cpsd+dcxucf
No

Conversion behavior with ICC profile

When an ICC profile is specified on a full-document export, format-specific bit-depth/color-mode conversion is skipped — the ICC transformation handles color management instead. See Color Mode and Bit Depth Validation tables above.

Output migration quick reference

For LLMs helping with migration:

  1. Always check output format before suggesting parameters:

    • JPEG → use quality (string enum)
    • PNG → use compression (string enum)
    • PSD/TIFF → no format-specific parameters
  2. Default recommendations:

    • JPEG: "photoshop_max" (highest quality, recommended default)
    • PNG: "default" (level 6 library standard; default when omitted)
  3. Common transformation pattern:

    // V1 to V2 transformation
    v1.outputs[0].href → v2.outputs[0].destination.url
    v1.outputs[0].type → v2.outputs[0].mediaType
    v1.outputs[0].quality (7) → v2.outputs[0].quality ("maximum")
    v1.outputs[0].storage → omit (for S3) or move to destination.storageType
    

Common migration issues and solutions

Category: Missing/incorrect fields

Issue: Missing outputs array

Problem:

{
  "image": {"source": {"url": "<URL>"}},
  "edits": {...}
  // Missing outputs!
}

Solution:

{
  "image": {"source": {"url": "<URL>"}},
  "edits": {...},
  "outputs": [
    {
      "destination": {"url": "<OUTPUT_URL>"},
      "mediaType": "image/jpeg"
    }
  ]
}

Issue: Unencoded characters in source/destination URLs

Problem: V1 was lenient with URL encoding; V2 validates URLs strictly across all endpoints.

{"image": {"source": {"url": "https://host/My Folder/file.psd"}}}

Solution: Percent-encode all special characters in every source.url and destination.url field.

{"image": {"source": {"url": "https://host/My%20Folder/file.psd"}}}

Spaces (%20) are the most common culprit. Error returned: "The url is malformed or blocked". Applies to all V2 endpoints.

Issue: String values for numeric fields (strict type coercion)

Problem: V1 accepted strings for numeric fields; V2 enforces strict types.

{"image": {"width": "1920", "height": "1080", "depth": "8"}}

Solution: Use integers/floats.

{"image": {"width": 1920, "height": 1080, "depth": 8}}

Commonly affected: image.width, image.height, image.depth, outputs[].width, outputs[].height, opacity, resolution.value.

Issue: Missing type field on layer in edits.layers[]

Problem: Layer entry has no type field — e.g., a visibility-only edit from V1 where type was inferred.

{"edits": {"layers": [{"name": "My Layer", "isVisible": false}]}}

Solution: type is required on every entry in edits.layers[]. V2 does not infer layer type.

{"edits": {"layers": [{"type": "layer", "name": "My Layer", "isVisible": false}]}}

Use the appropriate type: "layer", "text_layer", "adjustment_layer", "smart_object_layer", "group_layer", or "solid_color_layer".

Issue: Wrong media type property

Problem: Using type instead of mediaType

{
  "outputs": [{
    "destination": {...},
    "type": "image/jpeg"  // Wrong!
  }]
}

Solution:

{
  "outputs": [{
    "destination": {...},
    "mediaType": "image/jpeg"  // Correct!
  }]
}

Issue: Invalid quality values

Problem: Using V1 numeric quality (1-7)

{
  "quality": 7  // V1 format
}

Solution: Use V2 string values

{
  "quality": "maximum"  // V2 format
}

Valid V2 values: "very_poor", "poor", "low", "medium", "high", "maximum", "photoshop_max"

Note: V2 only accepts string enums. Numeric values from V1 are not supported.

Category: Storage issues

Issue: Using V1 storage parameter

Problem:

{
  "storage": "external"  // V1 format
}

Solution: Use destination structure

{
  "destination": {
    "url": "<URL>"
    // storageType only for Azure/Dropbox
  }
}

Issue: storageType confusion

Problem: Adding storageType for AWS S3

{
  "destination": {
    "url": "<S3_URL>",
    "storageType": "external"  // NOT needed!
  }
}

Solution: Omit storageType for standard URLs

{
  "destination": {
    "url": "<S3_URL>"
    // No storageType needed
  }
}

Only use storageType for:

Issue: Embedded storage for images

Problem: Using embedded for image output

{
  "destination": {
    "embedded": "base64"
  },
  "mediaType": "image/jpeg"  // Too large!
}

Solution: Use external or hosted storage for images

{
  "destination": {
    "url": "<OUTPUT_URL>"
  },
  "mediaType": "image/jpeg"
}

Embedded is only for XMP metadata and JSON manifests.

Category: Request structure issues

Issue: Using V1 input structure

Problem:

{
  "inputs": [
    {
      "href": "<URL>",
      "storage": "external"
    }
  ]
}

Solution: Use V2 image structure

{
  "image": {
    "source": {
      "url": "<URL>"
    }
  }
}

Issue: Wrong parameter names

Problem: Using V1 PascalCase

{
  "options": {
    "Exposure": 1.2,
    "Contrast": 10
  }
}

Solution: Use V2 camelCase with categories

{
  "edits": {
    "light": {
      "exposure": 1.2,
      "contrast": 10
    }
  }
}

Category: Layer operations

Issue: Missing operation type

Problem: Not specifying operation type when adding layer

{
  "edits": {
    "layers": [
      {
        "type": "layer",
        "name": "New Layer",
        "image": {
          "source": {"url": "<URL>"}
        }
        // Missing operation!
      }
    ]
  }
}

Solution: Specify operation type

{
  "edits": {
    "layers": [
      {
        "type": "layer",
        "name": "New Layer",
        "image": {
          "source": {"url": "<URL>"}
        },
        "operation": {
          "type": "add",
          "placement": {"type": "top"}
        }
      }
    ]
  }
}

Issue: Wrong layer type name

Problem: Using V1 layer type names

{
  "type": "textLayer"     // V1
  "type": "adjustmentLayer"  // V1
  "type": "smartObject"   // V1
}

Solution: Use V2 underscore format

{
  "type": "text_layer"           // V2
  "type": "adjustment_layer"     // V2
  "type": "smart_object_layer"   // V2
  "type": "solid_color_layer"    // V2
}

Issue: Incorrect placement syntax

Problem: Using V1 placement syntax

{
  "add": {
    "insertTop": true
  }
}

Solution: Use V2 operation structure

{
  "operation": {
    "type": "add",
    "placement": {
      "type": "top"
    }
  }
}

Category: XMP and masks issues

Issue: V1 mask structure not working

Problem: Using V1 mask structure with href and storage

{
  "edits": {
    "xmp": {
      "source": {"url": "<XMP_URL>"}
    },
    "masks": [
      {
        "href": "<MASK_URL>",
        "storage": "external",
        "digest": "37B3568A954F0C80CA946DA0187FB9E2"
      }
    ]
  }
}

Solution: Masks must be nested inside xmp object, not sibling to it, and use source structure

{
  "edits": {
    "xmp": {
      "source": {"url": "<XMP_URL>"},
      "masks": [
        {
          "source": {
            "url": "<MASK_URL>"
          },
          "digest": "37B3568A954F0C80CA946DA0187FB9E2"
        }
      ]
    }
  }
}

Issue: Inline XMP string not supported in V2

Problem: Using inline XMP string from V1

{
  "edits": {
    "xmp": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>..."
  }
}

Solution: V2 requires XMP to be provided as a file reference

{
  "edits": {
    "xmp": {
      "source": {
        "url": "<XMP_FILE_URL>"
      }
    }
  }
}

Or use Creative Cloud storage:

{
  "edits": {
    "xmp": {
      "source": {
        "creativeCloudPath": "/path/to/metadata.xmp"
      }
    }
  }
}

Issue: Missing mask digest

Problem: Providing mask without digest

{
  "masks": [
    {
      "source": {"url": "<MASK_URL>"}
    }
  ]
}

Solution: Each mask must include a digest for identification

{
  "masks": [
    {
      "source": {"url": "<MASK_URL>"},
      "digest": "37B3568A954F0C80CA946DA0187FB9E2"
    }
  ]
}

Issue: Using numeric orientation values

Problem: Using numeric EXIF codes for orientation

{
  "edits": {
    "xmp": {
      "source": {"url": "<XMP_URL>"},
      "orientation": 6
    }
  }
}

Solution: Use descriptive string values from the enum

{
  "edits": {
    "xmp": {
      "source": {"url": "<XMP_URL>"},
      "orientation": "right_top"
    }
  }
}

Valid orientation values: "top_left", "top_right", "bottom_right", "bottom_left", "left_top", "right_top", "right_bottom", "left_bottom"

Issue: Orientation at wrong nesting level

Problem: Placing orientation at edits level instead of within xmp

{
  "edits": {
    "xmp": {
      "source": {"url": "<XMP_URL>"}
    },
    "orientation": "right_top"
  }
}

Solution: Nest orientation inside the xmp object

{
  "edits": {
    "xmp": {
      "source": {"url": "<XMP_URL>"},
      "orientation": "right_top"
    }
  }
}

Category: Actions issues

Issue: Wrong ActionJSON format

Problem: Providing ActionJSON as object/array

{
  "source": {
    "content": [{"_obj": "..."}],  // Wrong: array
    "contentType": "application/json"
  }
}

Solution: Stringify the ActionJSON

{
  "source": {
    "content": "[{\"_obj\":\"...\"}]",  // Correct: string
    "contentType": "application/json"
  }
}

Issue: Missing contentType

Problem:

{
  "source": {
    "content": "[...]"
    // Missing contentType!
  }
}

Solution: Always specify contentType for inline content

{
  "source": {
    "content": "[...]",
    "contentType": "application/json"
  }
}

Issue: Wrong action structure

Problem: Using V1 action structure

{
  "options": {
    "actions": [
      {
        "href": "<URL>",
        "storage": "external"
      }
    ]
  }
}

Solution: Use V2 source structure

{
  "options": {
    "actions": [
      {
        "source": {
          "url": "<URL>"
        }
      }
    ]
  }
}

Category: Artboard issues

Issue: Incorrect input structure

Problem: Using v1 input format

{
  "inputs": [
    {"href": "<URL>", "storage": "external"}
  ]
}

Solution: Use v2 images structure

{
  "images": [
    {"source": {"url": "<URL>"}}
  ]
}

Issue: Too many images

Problem: Exceeding the maximum number of images (25).

Error: Array size must be between 1 and 25 at path 'images'

Solution: Ensure between 1 and 25 images in the request.

Issue: Too many outputs

Problem: Exceeding the maximum number of outputs (25).

Error: Array size must be between 1 and 25 at path 'outputs'

Solution: Ensure between 1 and 25 outputs in the request.

Issue: Missing required source

Problem: Missing source in image.

Error: Required field 'source' is missing at path 'images[0]'

Solution: Every image must have a source field.

{
  "images": [
    {"source": {"url": "<URL>"}}
  ]
}

Issue: Invalid hosted storage period

Problem: Hosted storage validityPeriod outside allowed range (60–86400 seconds).

Error: validityPeriod must be between 60 and 86400 seconds

Solution: Use a value between 60 (1 minute) and 86400 (24 hours).

{
  "destination": {"validityPeriod": 3600}
}

Issue: Missing storage type for Azure/Dropbox

Problem: Using Azure or Dropbox without specifying storageType.

Error: Azure Blob Storage URL requires 'storageType': 'azure'

Solution: Add storageType for Azure and Dropbox.

{
  "destination": {
    "url": "<AZURE_URL>",
    "storageType": "azure"
  }
}

Issue: Invalid JPEG quality value (artboard outputs)

Problem: Using numeric quality instead of string enum.

Error: Invalid value '7' at path 'outputs[0].quality'. Accepted values are: very_poor, poor, low, medium, high, maximum, photoshop_max

Solution: Use string enum values for quality.

{
  "mediaType": "image/jpeg",
  "quality": "maximum"
}

Category: Response handling

Issue: Looking for manifest in initial response

Problem: Expecting manifest data immediately

Solution: Poll status endpoint

# Step 1: Submit job
curl -X POST https://photoshop-api.adobe.io/v2/generate-manifest ...

# Response: {"jobId": "..."}

# Step 2: Poll status
curl -X GET https://photoshop-api.adobe.io/v2/status/{jobId} ...

# Response when complete:
# {
#   "status": "succeeded",
#   "result": {
#     "outputs": [
#       {"url": "..."}  // for hosted/url destinations
#       // OR
#       {"destination": {"embedded": "...", "content": {...}}}  // for embedded
#     ]
#   }
# }

Issue: Not handling all status values

Problem: Only checking succeeded/failed

if (status === 'succeeded') { ... }
else { /* assume failed */ }

Solution: Handle all status values

switch (status) {
  case 'succeeded':
    // Process result
    break;
  case 'failed':
    // Handle error
    break;
  case 'pending':
  case 'running':
    // Continue polling
    break;
  default:
    // Unknown status
}

Issue: Not handling error details array

Problem: Expecting single error message

console.error(status.error);  // Wrong

Solution: Iterate error details array

status.errorDetails.forEach(error => {
  console.error(`${error.errorCode}: ${error.message}`);
});

Code transformation examples

Example 1: Simple edit (auto tone)

V1 Code:

curl -X POST https://image.adobe.io/lrService/autoTone \
  -H "Authorization: Bearer $token" \
  -H "x-api-key: $apiKey" \
  -H "Content-Type: application/json" \
  -d '{
  "inputs": {
    "href": "https://my-storage.com/input.jpg",
    "storage": "external"
  },
  "outputs": [
    {
      "href": "https://my-storage.com/output.jpg",
      "storage": "external",
      "type": "image/jpeg"
    }
  ]
}'

V2 Code:

curl -X POST https://photoshop-api.adobe.io/v2/edit \
  -H "Authorization: Bearer $token" \
  -H "x-api-key: $apiKey" \
  -H "Content-Type: application/json" \
  -d '{
  "image": {
    "source": {
      "url": "https://my-storage.com/input.jpg"
    }
  },
  "edits": {
    "autoTone": true
  },
  "outputs": [
    {
      "destination": {
        "url": "https://my-storage.com/output.jpg"
      },
      "mediaType": "image/jpeg"
    }
  ]
}'

Key Transformations:

  1. Base URL: image.adobe.iophotoshop-api.adobe.io
  2. Endpoint: /lrService/autoTone/v2/edit
  3. Input: inputs.hrefimage.source.url
  4. Operation: Implicit → edits.autoTone: true
  5. Output: outputs[].hrefoutputs[].destination.url
  6. Type: typemediaType
  7. Storage: Removed (not needed for standard URLs)

Example 2: Complex edit (multiple adjustments)

V1 Code (3 separate calls):

# Call 1
curl -X POST https://image.adobe.io/lrService/autoTone ...

# Call 2
curl -X POST https://image.adobe.io/lrService/autoStraighten ...

# Call 3
curl -X POST https://image.adobe.io/lrService/edit \
  -d '{
    "inputs": {...},
    "options": {
      "Exposure": 1.2,
      "Contrast": 10,
      "Saturation": 15
    },
    "outputs": [...]
  }'

V2 Code (single call):

curl -X POST https://photoshop-api.adobe.io/v2/edit \
  -H "Authorization: Bearer $token" \
  -H "x-api-key: $apiKey" \
  -H "Content-Type: application/json" \
  -d '{
  "image": {
    "source": {
      "url": "<INPUT_URL>"
    }
  },
  "edits": {
    "autoTone": true,
    "autoStraighten": {
      "enabled": true,
      "constrainCrop": true
    },
    "light": {
      "exposure": 1.2,
      "contrast": 10
    },
    "color": {
      "saturation": 15
    }
  },
  "outputs": [
    {
      "destination": {
        "url": "<OUTPUT_URL>"
      },
      "mediaType": "image/jpeg"
    }
  ]
}'

Key Transformations:

  1. Three calls → One call
  2. Operations combined in edits object
  3. PascalCase params → camelCase within categories
  4. Exposurelight.exposure
  5. Saturationcolor.saturation

Example 3: Format conversion (PSD to JPEG)

V1 Code:

curl -X POST https://image.adobe.io/pie/psdService/renditionCreate \
  -H "Authorization: Bearer $token" \
  -H "x-api-key: $apiKey" \
  -H "Content-Type: application/json" \
  -d '{
  "inputs": [
    {
      "href": "https://my-storage.com/document.psd",
      "storage": "external"
    }
  ],
  "outputs": [
    {
      "href": "https://my-storage.com/output.jpg",
      "storage": "external",
      "type": "image/jpeg",
      "quality": 7,
      "width": 1920
    }
  ]
}'

V2 Code:

curl -X POST https://photoshop-api.adobe.io/v2/create-composite \
  -H "Authorization: Bearer $token" \
  -H "x-api-key: $apiKey" \
  -H "Content-Type: application/json" \
  -d '{
  "image": {
    "source": {
      "url": "https://my-storage.com/document.psd"
    }
  },
  "outputs": [
    {
      "destination": {
        "url": "https://my-storage.com/output.jpg"
      },
      "mediaType": "image/jpeg",
      "quality": "maximum",
      "width": 1920
    }
  ]
}'

Key Transformations:

  1. Endpoint: /pie/psdService/renditionCreate/v2/create-composite
  2. Input: inputs[0].hrefimage.source.url
  3. Output: outputs[0].hrefoutputs[0].destination.url
  4. Quality: Numeric 7 → String "maximum"
  5. Storage: Removed (not needed)
  6. No edits block (just format conversion)

Example 4: Layer addition

V1 Code:

curl -X POST https://image.adobe.io/pie/psdService/documentOperations \
  -d '{
  "inputs": [{
    "href": "<DOCUMENT_URL>",
    "storage": "external"
  }],
  "options": {
    "layers": [
      {
        "type": "layer",
        "name": "New Image",
        "add": {
          "insertTop": true
        },
        "input": {
          "href": "<IMAGE_URL>",
          "storage": "external"
        }
      }
    ]
  },
  "outputs": [{
    "href": "<OUTPUT_URL>",
    "storage": "external",
    "type": "image/vnd.adobe.photoshop"
  }]
}'

V2 Code:

curl -X POST https://photoshop-api.adobe.io/v2/create-composite \
  -H "Authorization: Bearer $token" \
  -H "x-api-key: $apiKey" \
  -H "Content-Type: application/json" \
  -d '{
  "image": {
    "source": {
      "url": "<DOCUMENT_URL>"
    }
  },
  "edits": {
    "layers": [
      {
        "type": "layer",
        "name": "New Image",
        "image": {
          "source": {
            "url": "<IMAGE_URL>"
          }
        },
        "operation": {
          "type": "add",
          "placement": {
            "type": "top"
          }
        }
      }
    ]
  },
  "outputs": [
    {
      "destination": {
        "url": "<OUTPUT_URL>"
      },
      "mediaType": "image/vnd.adobe.photoshop"
    }
  ]
}'

Key Transformations:

  1. Endpoint: /documentOperations/create-composite
  2. Structure: options.layersedits.layers
  3. Add operation: add: {insertTop: true}operation: {type: "add", placement: {type: "top"}}
  4. Layer source: input.hrefimage.source.url

Example 4b: Layer edit operation

V1 Code (with edit: {}):

curl -X POST https://image.adobe.io/pie/psdService/documentOperations \
  -d '{
  "inputs": [{
    "href": "<DOCUMENT_URL>",
    "storage": "external"
  }],
  "options": {
    "layers": [
      {
        "id": 123,
        "type": "layer",
        "edit": {},
        "text": {
          "content": "Updated text"
        }
      }
    ]
  },
  "outputs": [{
    "href": "<OUTPUT_URL>",
    "storage": "external",
    "type": "image/vnd.adobe.photoshop"
  }]
}'

V2 Code (explicit edit operation):

curl -X POST https://photoshop-api.adobe.io/v2/create-composite \
  -H "Authorization: Bearer $token" \
  -H "x-api-key: $apiKey" \
  -H "Content-Type: application/json" \
  -d '{
  "image": {
    "source": {
      "url": "<DOCUMENT_URL>"
    }
  },
  "edits": {
    "layers": [
      {
        "id": 123,
        "type": "text_layer",
        "text": {
          "content": "Updated text"
        },
        "operation": {
          "type": "edit"
        }
      }
    ]
  },
  "outputs": [
    {
      "destination": {
        "url": "<OUTPUT_URL>"
      },
      "mediaType": "image/vnd.adobe.photoshop"
    }
  ]
}'

Key Transformations:

  1. Layer type: type: "layer"type: "text_layer" (specify actual layer type)
  2. Operation: V1 "edit": {} → V2 "operation": {"type": "edit"}
  3. Edit operation is now explicit and required (not implicit)
  4. When V1 layer has "edit": {} with no add/delete, use edit operation in V2

Example 5: Action execution

V1 Code:

curl -X POST https://image.adobe.io/pie/psdService/photoshopActions \
  -d '{
  "inputs": [{
    "href": "<IMAGE_URL>",
    "storage": "external"
  }],
  "options": {
    "actions": [
      {
        "href": "<ACTION_FILE_URL>",
        "storage": "external"
      }
    ]
  },
  "outputs": [{
    "href": "<OUTPUT_URL>",
    "storage": "external",
    "type": "image/vnd.adobe.photoshop"
  }]
}'

V2 Code:

curl -X POST https://photoshop-api.adobe.io/v2/execute-actions \
  -H "Authorization: Bearer $token" \
  -H "x-api-key: $apiKey" \
  -H "Content-Type: application/json" \
  -d '{
  "image": {
    "source": {
      "url": "<IMAGE_URL>"
    }
  },
  "options": {
    "actions": [
      {
        "source": {
          "url": "<ACTION_FILE_URL>"
        }
      }
    ]
  },
  "outputs": [
    {
      "destination": {
        "url": "<OUTPUT_URL>"
      },
      "mediaType": "image/vnd.adobe.photoshop"
    }
  ]
}'

Key Transformations:

  1. Endpoint: /photoshopActions/execute-actions
  2. Input: inputs[0].hrefimage.source.url
  3. Action: actions[0].hrefactions[0].source.url
  4. Storage: Removed from action and input

Example 6: Status checking

V1 Code:

# Lightroom job
curl -X GET https://image.adobe.io/lrService/status/{jobId} \
  -H "Authorization: Bearer $token" \
  -H "x-api-key: $apiKey"

# Photoshop job
curl -X GET https://image.adobe.io/pie/psdService/status/{jobId} \
  -H "Authorization: Bearer $token" \
  -H "x-api-key: $apiKey"

V2 Code:

# All jobs use same endpoint
curl -X GET https://photoshop-api.adobe.io/v2/status/{jobId} \
  -H "Authorization: Bearer $token" \
  -H "x-api-key: $apiKey"

Key Transformations:

  1. Service-specific endpoints → Unified endpoint
  2. Same jobId works for all operation types

Example 7: Artboard create

V1 Code:

curl -X POST https://image.adobe.io/pie/psdService/artboardCreate \
  -H "Authorization: Bearer $token" \
  -H "x-api-key: $apiKey" \
  -H "Content-Type: application/json" \
  -d '{
  "inputs": [
    {"href": "<IMAGE_1_URL>", "storage": "external"},
    {"href": "<IMAGE_2_URL>", "storage": "external"}
  ],
  "outputs": [
    {
      "href": "<SIGNED_POST_URL>",
      "storage": "external",
      "type": "image/vnd.adobe.photoshop"
    }
  ]
}'

V2 Code:

curl -X POST https://photoshop-api.adobe.io/v2/create-artboard \
  -H "Authorization: Bearer $token" \
  -H "x-api-key: $apiKey" \
  -H "Content-Type: application/json" \
  -d '{
  "images": [
    {"source": {"url": "<IMAGE_1_URL>"}},
    {"source": {"url": "<IMAGE_2_URL>"}}
  ],
  "outputs": [
    {
      "destination": {"url": "<SIGNED_POST_URL>"},
      "mediaType": "image/vnd.adobe.photoshop"
    }
  ]
}'

Key Transformations:

  1. Endpoint: /pie/psdService/artboardCreate/v2/create-artboard
  2. Input: inputs[]images[]; href + storagesource.url
  3. Output: outputs[].hrefoutputs[].destination.url; typemediaType
  4. Optional: Add artboardSpacing (integer, pixels) for horizontal spacing between artboards (default 50)
  5. Storage: Omit for standard URLs; use destination.storageType only for Azure/Dropbox

V2 status response output patterns

Overview

The V2 status response structure varies significantly from V1, particularly in how outputs are represented. Understanding these patterns is crucial for correct migration, as the result.outputs[] field structure depends on the destination type specified in the original request.

Base response structure

All V2 status responses share this base structure regardless of destination type:

{
  "jobId": "550e8400-e29b-41d4-a716-446655440000",
  "createdTime": "2025-11-11T10:00:00.000Z",
  "modifiedTime": "2025-11-11T10:05:30.000Z",
  "status": "succeeded",
  "result": {
    "outputs": [...]  // Structure varies by destination type
  }
}

Key structural changes from V1

V1 Field
V2 Field
Notes
created
createdTime
ISO-8601 datetime format
modified
modifiedTime
ISO-8601 datetime format
_links.self.href
removed
No hypermedia links in V2
outputs (top-level)
result.outputs
Nested inside result object
outputs[].status
status (job-level)
Single status for entire job, not per-output
outputs[]._links
removed
No per-output hypermedia links
outputs[].input
removed
No input echo in outputs

Status Values: "pending", "running", "succeeded", "failed"

Destination type patterns

The result.outputs[] structure varies based on the destination type specified in the request. Here are all 5 destination types and their status response patterns:

1. External URL destination (presigned URLs)

Use Case: AWS S3, Azure Blob, Dropbox, Frame.io, Google Drive presigned URLs

Request:

{
  "outputs": [{
    "destination": {
      "url": "https://s3.amazonaws.com/bucket/output.jpg?X-Amz-Signature=..."
    },
    "mediaType": "image/jpeg",
    "quality": "maximum"
  }]
}

Status Response (succeeded):

{
  "jobId": "...",
  "createdTime": "2025-11-11T10:00:00.000Z",
  "modifiedTime": "2025-11-11T10:05:30.000Z",
  "status": "succeeded",
  "result": {
    "outputs": [{
      "destination": {
        "url": "https://s3.amazonaws.com/bucket/output.jpg?X-Amz-Signature=..."
      },
      "mediaType": "image/jpeg",
      "quality": "maximum"
    }]
  }
}

Pattern: The output echoes back the destination URL exactly as provided. The file has been written to the presigned URL.

V1 vs V2 Comparison:

// V1 Status Response
{
  outputs: [{
    _links: {
      self: { href: "https://s3.amazonaws.com/bucket/output.jpg?..." }
    },
    input: "https://input-url...",
    status: "succeeded"
  }]
}

// V2 Status Response
{
  result: {
    outputs: [{
      destination: { url: "https://s3.amazonaws.com/bucket/output.jpg?..." },
      mediaType: "image/jpeg",
      quality: "maximum"
    }]
  },
  status: "succeeded"  // Job-level, not per-output
}

Key Migration Points:

2. Embedded destination (inline content)

Use Case: XMP metadata, JSON manifests, small text outputs returned inline

Request (embedded: "string"):

{
  "outputs": [{
    "destination": {
      "embedded": "string"
    },
    "mediaType": "application/rdf+xml"
  }]
}

Status Response:

{
  "status": "succeeded",
  "result": {
    "outputs": [{
      "destination": {
        "embedded": "string",
        "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<x:xmpmeta xmlns:x=\"adobe:ns:meta/\">...</x:xmpmeta>"
      },
      "mediaType": "application/rdf+xml"
    }]
  }
}

Request (embedded: "json"):

{
  "outputs": [{
    "destination": {
      "embedded": "json"
    },
    "mediaType": "application/json"
  }]
}

Status Response:

{
  "result": {
    "outputs": [{
      "destination": {
        "embedded": "json",
        "content": {
          "document": {
            "width": 1920,
            "height": 1080,
            "layers": [...]
          }
        }
      },
      "mediaType": "application/json"
    }]
  }
}

Request (embedded: "base64"):

{
  "outputs": [{
    "destination": {
      "embedded": "base64"
    },
    "mediaType": "image/png"
  }]
}

Status Response:

{
  "result": {
    "outputs": [{
      "destination": {
        "embedded": "base64",
        "content": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
      },
      "mediaType": "image/png"
    }]
  }
}

Pattern: Destination includes content field with actual data inline.

Embedded Types:

V1 vs V2:

// V1: Limited embedded support, varied formats
{
  outputs: [{
    _links: { self: { href: "data:..." } },
    status: "succeeded"
  }]
}

// V2: Standardized embedded format with content field
{
  result: {
    outputs: [{
      destination: {
        embedded: "string",
        content: "<?xml version=..."
      },
      mediaType: "application/rdf+xml"
    }]
  }
}

Important Restrictions:

3. Hosted destination (Adobe temporary storage)

Use Case: Temporary storage without managing your own presigned URLs (V2 new feature)

Request:

{
  "outputs": [{
    "destination": {
      "validityPeriod": 3600  // seconds: 60-86400 (1 min - 24 hours)
    },
    "mediaType": "image/jpeg",
    "quality": "high"
  }]
}

Status Response:

{
  "status": "succeeded",
  "result": {
    "outputs": [{
      "destination": {
        "url": "https://adobe-hosted-storage.example.com/path/to/output.jpg?expires=1699700000&signature=abc123..."
      },
      "mediaType": "image/jpeg",
      "quality": "high"
    }]
  }
}

Pattern: Adobe generates a temporary presigned URL valid for the specified period (max 24 hours). The URL is returned in destination.url.

Key Characteristics:

CRITICAL: Download the file before the URL expires! No recovery after expiration.

V1 Equivalent: None - this is a new V2 feature.

Use Cases:

4. Creative Cloud destination

Use Case: Direct integration with Creative Cloud storage (ACP - Adobe Content Platform)

Request:

{
  "outputs": [{
    "destination": {
      "creativeCloudPath": "my-folder/output.jpg"
    },
    "mediaType": "image/jpeg",
    "quality": "maximum"
  }]
}

Status Response:

{
  "status": "succeeded",
  "result": {
    "outputs": [{
      "destination": {
        "creativeCloudPath": "my-folder/output.jpg"
      },
      "mediaType": "image/jpeg",
      "quality": "maximum"
    }]
  }
}

Pattern: The output echoes back the Creative Cloud path. File has been written to CC storage (ACP).

Alternative: Creative Cloud File ID

// Request
{
  "destination": {
    "creativeCloudFileId": "urn:aaid:sc:VA6C2:...",
    "creativeCloudProjectId": "urn:aaid:sc:VA6C2:..."  // Optional
  }
}

// Status Response
{
  "destination": {
    "creativeCloudFileId": "urn:aaid:sc:VA6C2:...",
    "creativeCloudProjectId": "urn:aaid:sc:VA6C2:..."
  }
}

V1 vs V2:

// V1 Status Response (CC storage)
{
  outputs: [{
    _links: {
      self: {
        href: "my-folder/output.jpg",
        storage: "external"
      }
    },
    status: "succeeded"
  }]
}

// V2 Status Response (CC storage)
{
  result: {
    "outputs": [{
      destination: {
        creativeCloudPath: "my-folder/output.jpg"
      },
      mediaType: "image/jpeg"
    }]
  },
  status: "succeeded"
}

When to Use:

5. Script output pattern (dynamic file discovery)

Use Case: UXP scripts that generate files dynamically (V2 new feature, /v2/execute-actions only)

Request with glob pattern:

{
  "outputs": [{
    "destination": {
      "validityPeriod": 3600
    },
    "mediaType": "application/json",
    "scriptOutputPattern": "*.json"  // Matches any JSON files
  }]
}

Status Response when script generates 3 JSON files:

{
  "status": "succeeded",
  "result": {
    "outputs": [
      {
        "destination": {
          "url": "https://adobe-hosted.../fonts.json?expires=..."
        },
        "mediaType": "application/json"
      },
      {
        "destination": {
          "url": "https://adobe-hosted.../layers.json?expires=..."
        },
        "mediaType": "application/json"
      },
      {
        "destination": {
          "url": "https://adobe-hosted.../metadata.json?expires=..."
        },
        "mediaType": "application/json"
      }
    ]
  }
}

Pattern: One request output with pattern expands to multiple status response outputs (one per matched file).

Exact filename match:

{
  "scriptOutputPattern": "document-info.json"  // Exact match
}
// Status: Single output with that specific file

Glob patterns supported:

Pattern Expansion Examples:

1 request output → 1 status output (exact match):

// Request
{"scriptOutputPattern": "metadata.json"}

// Status
{"outputs": [{"destination": {"url": ".../metadata.json?..."}}]}

1 request output → 3 status outputs (glob match):

// Request
{"scriptOutputPattern": "*.json"}

// Status (if 3 JSON files generated)
{"outputs": [
  {"destination": {"url": ".../file1.json?..."}},
  {"destination": {"url": ".../file2.json?..."}},
  {"destination": {"url": ".../file3.json?..."}}
]}

Restrictions:

MediaType Filtering: If mediaType: "application/json", only files with .json extension are included, even if pattern matches others.

V1 Equivalent: None - this is a new V2 feature for UXP script workflows.

Use Cases:

Multiple outputs example

When multiple outputs with different destinations are requested:

Request:

{
  "outputs": [
    {
      "destination": {"url": "https://s3.../output.jpg?..."},
      "mediaType": "image/jpeg",
      "quality": "maximum",
      "width": 2400
    },
    {
      "destination": {"embedded": "json"},
      "mediaType": "application/json"
    },
    {
      "destination": {"creativeCloudPath": "folder/output.png"},
      "mediaType": "image/png",
      "compression": "high"
    },
    {
      "destination": {"validityPeriod": 7200},
      "mediaType": "image/tiff"
    }
  ]
}

Status Response:

{
  "status": "succeeded",
  "result": {
    "outputs": [
      {
        "destination": {"url": "https://s3.../output.jpg?..."},
        "mediaType": "image/jpeg",
        "quality": "maximum",
        "width": 2400
      },
      {
        "destination": {
          "embedded": "json",
          "content": {"document": {"width": 1920, "height": 1080}}
        },
        "mediaType": "application/json"
      },
      {
        "destination": {"creativeCloudPath": "folder/output.png"},
        "mediaType": "image/png",
        "compression": "high"
      },
      {
        "destination": {"url": "https://adobe-hosted.../output.tiff?expires=..."},
        "mediaType": "image/tiff"
      }
    ]
  }
}

Pattern: Each output maintains its destination type and parameters. Outputs array order is preserved.

Failed job response

When job fails, no result field is present:

{
  "jobId": "550e8400-e29b-41d4-a716-446655440000",
  "createdTime": "2025-11-11T10:00:00.000Z",
  "modifiedTime": "2025-11-11T10:05:35.000Z",
  "status": "failed",
  "errorDetails": [
    {
      "errorCode": "400401",
      "message": "The value provided is not valid."
    },
    {
      "errorCode": "400420",
      "message": "Required fields missing on the API request."
    }
  ]
}

Pattern: Failed jobs have errorDetails array instead of result object.

Pending/running job response

While job is processing, result field may be absent or incomplete:

{
  "jobId": "550e8400-e29b-41d4-a716-446655440000",
  "createdTime": "2025-11-11T10:00:00.000Z",
  "modifiedTime": "2025-11-11T10:00:15.000Z",
  "status": "running"
}

Pattern: No result field until job completes. Poll endpoint until status is "succeeded" or "failed".

Recommended polling interval: 5 seconds

V1 to V2 status response quick reference table

Request Destination Type
V1 Status Response
V2 Status Response
Status Field Location
External URL (S3, Azure, etc.)
outputs[]._links.self.href: "<URL>"
result.outputs[].destination.url: "<URL>"
result.outputs[].destination.url
Embedded (string/json/base64)
Limited/Not standard
result.outputs[].destination.{embedded, content}
result.outputs[].destination.content
Hosted (Adobe temp storage)
N/A (V2 new feature)
result.outputs[].destination.url: "<TEMP_URL>"
result.outputs[].destination.url
Creative Cloud Path
outputs[]._links.self.href: "path/to/file"
result.outputs[].destination.creativeCloudPath: "path/to/file"
result.outputs[].destination.creativeCloudPath
Creative Cloud File ID
outputs[]._links.self.href: "urn:aaid:..."
result.outputs[].destination.creativeCloudFileId: "urn:aaid:..."
result.outputs[].destination.creativeCloudFileId
Script Pattern (glob)
N/A (V2 new feature)
Expands to multiple outputs, one per matched file
Multiple result.outputs[] entries

Key migration insights

1. Response Structure Mirror: In V2, the status response result.outputs[] structure directly mirrors the request outputs[] structure:

2. Status Location:

3. Hypermedia Links:

4. Destination Determination: The destination type in status response is determined by request:

5. Output Array Expansion: Only scriptOutputPattern causes output expansion (1 request → N response outputs). All other destination types maintain 1:1 mapping.

Migration validation points

When migrating status polling code:

Update field references:

Update status checking:

Remove hypermedia logic:

Handle destination types:

Error handling:

Validation checklist

Use this checklist when migrating or validating V1 → V2 code:

Request structure

Edit operations specific

Layer operations specific

Adjustment layer operations specific

Text layer operations specific

Smart object layer operations specific

Text endpoint migration specific (/pie/psdService/text)

Action operations specific

Artboard operations specific

Storage configuration

Output configuration

Output configuration: JPEG specific

Output configuration: PNG specific

Output configuration: PSD/TIFF specific

Document creation specific

Export layers specific

ICC profile specific

Status checking

Authentication

Appendix: Common error codes

Error Code
Description
Common Cause
Solution
400400
Invalid JSON in request payload
Malformed JSON
Validate JSON syntax
400401
Invalid value provided
Wrong parameter value/type
Check parameter documentation
400410
Required fields missing
Missing required field
Add required fields (e.g., outputs array)
400411
Request blocked by content filters
Inappropriate content
Review content policies
400420
Required fields missing on API request
Missing critical parameter
Check all required fields present
403401
User not authorized
Invalid/expired token
Refresh access token
403421
Quota exhausted
API limits exceeded
Check quota, upgrade plan
404
Job not found
Invalid jobId
Verify jobId is correct
422404
Unable to process outputs
Output destination issue
Check output URLs, storage permissions
500600
Internal server error
Server-side issue
Retry request, contact support if persists

Quick command reference

Complete migration example

V1 (Multiple Calls):

# Call 1: Auto tone
curl -X POST https://image.adobe.io/lrService/autoTone \
  -H "Authorization: Bearer $token" \
  -H "x-api-key: $apiKey" \
  -d '{"inputs": {"href": "<URL>"}, "outputs": [...]}'

# Call 2: Status check
curl -X GET https://image.adobe.io/lrService/status/{jobId1} \
  -H "Authorization: Bearer $token" \
  -H "x-api-key: $apiKey"

# Call 3: Apply edits
curl -X POST https://image.adobe.io/lrService/edit \
  -H "Authorization: Bearer $token" \
  -H "x-api-key: $apiKey" \
  -d '{"inputs": {"href": "<URL>"}, "options": {...}, "outputs": [...]}'

# Call 4: Status check
curl -X GET https://image.adobe.io/lrService/status/{jobId2} \
  -H "Authorization: Bearer $token" \
  -H "x-api-key: $apiKey"

V2 (Single Call):

# Single combined request
curl -X POST https://photoshop-api.adobe.io/v2/edit \
  -H "Authorization: Bearer $token" \
  -H "x-api-key: $apiKey" \
  -H "Content-Type: application/json" \
  -d '{
  "image": {
    "source": {
      "url": "<INPUT_URL>"
    }
  },
  "edits": {
    "autoTone": true,
    "light": {
      "exposure": 1.2,
      "contrast": 10
    },
    "color": {
      "saturation": 15
    }
  },
  "outputs": [
    {
      "destination": {
        "url": "<OUTPUT_URL>"
      },
      "mediaType": "image/jpeg",
      "quality": "high"
    }
  ]
}'

# Single status check
curl -X GET https://photoshop-api.adobe.io/v2/status/{jobId} \
  -H "Authorization: Bearer $token" \
  -H "x-api-key: $apiKey"

Document version

Version: 1.19 Created: October 29, 2025 Last Updated: May 6, 2026

Coverage:

Composite API (/v2/create-composite) — key breaking changes reference

This section consolidates the highest-impact breaking changes across all four V1 composite-family endpoints (documentCreate, documentOperations, renditionCreate, smartObject).

For the complete authoritative field-level diff, see Create Composite V1 Reference.

V1 endpoint to V2 mapping

All four V1 endpoints map to POST /v2/create-composite.

V1 Endpoint
Purpose
documentCreate
Create new blank PSD
documentOperations
Document ops + layer manipulation
renditionCreate
Export to JPEG/PNG/PSD/TIFF
smartObject
Smart object operations

Top-level request envelope

V1
V2
inputs[].href + storage
image.source.url
options.document.*
image.* (top-level)
options.layers[]
edits.layers[]
options.* (doc ops)
edits.document.*
outputs[].href
outputs[].destination.url
outputs[].type
outputs[].mediaType

Layer processing order: V1 = bottom-up (last first); V2 = top-down (first first). Referenced layers must appear earlier in the array in V2.

Layer type renames

V1 type
V2 type
"adjustmentLayer"
"adjustment_layer"
"backgroundLayer"
"background_layer" (supports add, edit, delete, convert-to-layer; move not supported)
"fillLayer"
"solid_color_layer"
"layer"
"layer" (unchanged)
"layerSection"
"group_layer"
"smartObject"
"smart_object_layer"
"textLayer"
"text_layer"

Common layer property changes

V1
V2
Impact
locked: true/false
protection: ["all"]/["none"]
Breaking
visible
isVisible
Breaking
blendOptions.opacity
opacity (top-level)
Breaking: de-nested
blendOptions.blendMode
blendMode (top-level)
Breaking: de-nested
bounds: {left, top, width, height}
transform: {offset: {horizontal, vertical}, dimension: {width, height}}
Breaking
mask.clip: true
isClipped: true (top-level)
Breaking
mask.linked/enabled/input
pixelMask.linked/enabled/source.url
Breaking (also: prefix changed from mask to pixelMask)
fillToCanvas: true
Not supported
Feature gap

Text layer critical changes

Change
V1
V2
Character style range field
from, to direct on item
apply.from, apply.to
to semantics
Length
Inclusive end index (off-by-one!)
Font name
fontName: "PSName"
characterStyle.font.postScriptName
Style wrapping
Direct properties
Inside characterStyle: {...}
Paragraph style wrapping
Direct properties
Inside paragraphStyle: {...}
Text frame bounds
Layer-level bounds
text.frame.type: "area" with {top, left, right, bottom}
Default frame
Area at (0,0,4,4)
Point frame at canvas center
Font options
options.fonts, options.globalFont, options.manageMissingFonts
fontOptions.additionalFonts, .defaultFontPostScriptName, .missingFontStrategy
Missing font strategy
"useDefault"
"use_default" (Breaking: underscore)

Font color value ranges

V1 accepted 0–65535 for all fontColor components. V2 enforces lower maximums:

Color Model
V1 Fields
V2 Fields
V1 Range
V2 Range
If V1 value exceeds V2 max
rgb
red, green, blue
red, green, blue
0–65535
0–32768
Use 32768
cmyk
cyan, magenta, yellowColor, black
cyan, magenta, yellow, black
0–65535
0–32768
Use 32768
lab
luminance
l
0–65535
0–32768
Use 32768
lab
a, b
a, b
0–65535
-16384–16384
Use 16384
gray
gray
gray
0–65535
0–32768
Use 32768

All components are required and default to 0 when omitted. Usage: fontColor.rgb, fontColor.cmyk, fontColor.lab, fontColor.gray.

V2 rejects requests where any component exceeds its maximum. Cap values before sending:

Adjustment layer critical changes

Change
V1
V2
adjustments.type
Not present (type inferred from key)
Required — snake_case enum
brightnessContrast type
(implicit)
type: "brightness_contrast"
hueSaturation type
(implicit)
type: "hue_saturation"
colorBalance type
(implicit)
type: "color_balance"
Exposure amount field
exposure.exposure
exposure.exposureValue

Solid color layer critical changes

V1
V2
Impact
fills[0].solidColor.rgb.red/green/blue
fill.solidColor.red/green/blue
Breaking: plural→singular, rgb wrapper removed

V1 used fills (plural array) with nested solidColor.rgb wrapper. V2 uses fill (singular object) with solidColor directly containing color components.

V1: {"fills": [{"solidColor": {"rgb": {"red": 255, "green": 0, "blue": 0}}}]}

V2: {"fill": {"solidColor": {"red": 255, "green": 0, "blue": 0}}}

Error when using V1 structure: "edits.layers[0].fill: fill is required when adding a solid color layer".

Output format changes

Feature
V1
V2
JPEG quality
Numeric 1–7
String enum: "low", "medium", "high", "maximum", "photoshop_max"
PNG compression
"small", "medium", "large"
"maximum", "medium", "low" (and more)
Storage: external
"storage": "external"
destination: {url: "..."}
Storage: CC
"storage": "adobe"
destination: {creativeCloudPath: "..."}
ICC profile
Not supported
iccProfile on output (new)
Hosted storage
Not supported
destination: {validityPeriod: N} (new)

Features missing in V2

Feature
Notes
fillToCanvas
Not supported
Canvas size adjustment
Coming soon
Image rotation
Coming soon
Trim by color
Not supported
TIFF output
Status unclear

Artboard API (/v2/create-artboard) — key breaking changes reference

For the complete authoritative field-level diff, see Create Artboard V1 Reference.

Endpoint

V1
V2
POST /pie/psdService/artboardCreate
POST /v2/create-artboard
https://image.adobe.io
https://photoshop-api.adobe.io

Request envelope (breaking change)

data-variant=warning
data-slots=text
V1 input was options.artboard[], not inputs[]. The existing artboard-migration.md guide shows an incorrect V1 example using inputs[]. The actual V1 schema used options.artboard[]. The complete options wrapper is removed in V2.
V1
V2
options.artboard[n].href + storage
images[n].source.url
options container
Removed entirely
Max images: undocumented
Max images: 25 (enforced)
(not in V1)
artboardSpacing (integer pixels, default 50)

Input storage mapping

V1 storage
V2 source field
"external"
source.url
"adobe" (CC path)
source.creativeCloudPath (no leading slash)
"adobe" (URN)
source.creativeCloudFileId
"acp"
source.url
"azure"
source.url
"dropbox"
source.url
"cclib"
source.creativeCloudFileId

Output field changes

V1
V2
Breaking?
href + storage
destination.url (or CC / hosted / embedded)
Yes
type
mediaType
Yes
quality 1–7 (number)
quality string enum
Yes
compression small/medium/large
compression 10-level enum
Yes
overwrite
shouldOverwrite
Yes (renamed)
trimToCanvas: true
cropMode: "document_bounds" or "trim_to_transparency"
Yes
trimToLayer: true
cropMode: "layer_bounds"
Yes
(not in V1)
height
New

JPEG quality mapping

V1 (number)
V2 (string)
7
"maximum"
5–6
"high"
3–4
"medium"
1–2
"poor" / "very_poor"
(new)
"photoshop_max" (recommended default)

PNG compression mapping

V1
V2
"small"
"maximum"
"medium"
"medium"
"large"
"low"

New output formats in V2

document/vnd.adobe.cpsd+dcxucf (PSDC), application/json (JSON manifest) — not in V1.

Status response changes

V1
V2
outputs[n].status
status (top-level)
outputs[n].created/modified
createdTime/modifiedTime (top-level)
outputs[n]._links.renditions[m].href
result.outputs[n].destination.url
outputs[n].errors.code (number)
errorDetails[n].errorCode (string)

Execute Actions API (/v2/execute-actions) — key breaking changes reference

For the complete authoritative field-level diff, see Execute Actions V1 Reference.

Endpoints covered

V1
V2
POST /pie/psdService/photoshopActions
POST /v2/execute-actions
POST /pie/psdService/actionJSON
POST /v2/execute-actions
POST /pie/psdService/productCrop
POST /v2/execute-actions (published action)
POST /pie/psdService/splitView
POST /v2/execute-actions (published action)
POST /pie/psdService/sideBySide
POST /v2/execute-actions (published action)
POST /pie/psdService/depthBlur
Not yet supported (Neural Filters)
POST /pie/psdService/text
POST /v2/execute-actions (ActionJSON or UXP)
POST /pie/psdService/smartObjectV2
Not execute-actions — use document edit operations

The only known API-visible gap is Neural Filters (Depth Blur), which are not yet available in V2.

Request envelope (breaking)

V1
V2
inputs[0].href
image.source.url
inputs[0].storage: "external"
(omit — inferred)
inputs[0].storage: "adobe" (path)
image.source.creativeCloudPath
inputs[0].storage: "adobe" (URN)
image.source.creativeCloudFileId
outputs[].href
outputs[].destination.url
outputs[].storage
outputs[].destination.storageType (Azure/Dropbox only)
outputs[].type
outputs[].mediaType

ActionJSON format (critical breaking change)

V1 options.actionJSON was an array of inline JSON objects. V2 requires ActionJSON as a stringified JSON string inside options.actions[].source.content with contentType: "application/json".

// V1
{"options": {"actionJSON": [{"_obj": "convertMode", "to": {"_enum": "colorSpaceMode", "_value": "grayscale"}}]}}

// V2
{"options": {"actions": [{"source": {"content": "[{\"_obj\":\"convertMode\",\"to\":{\"_enum\":\"colorSpaceMode\",\"_value\":\"grayscale\"}}]", "contentType": "application/json"}}]}}

Additional images/contents rename

V1
V2
Field name
options.additionalImages[]
options.additionalContents[]
Item structure
{href, storage}
{source: {url}}
Placeholder
ACTION_JSON_OPTIONS_ADDITIONAL_IMAGES_0
__ADDITIONAL_CONTENT_0__

Use __ADDITIONAL_CONTENT_N__ where N is the exact index from the original V1 placeholder — do NOT renumber. The old ACTION_JSON_OPTIONS_ADDITIONAL_IMAGES_N format is accepted by V2 for backward compatibility but __ADDITIONAL_CONTENT_N__ is the authoritative format per the V2 API schema.

.atn action source nesting

V1
V2
options.actions[].href
options.actions[].source.url
options.actions[].storage
(implicit from source field)

V1 supported a single action per request. V2 allows up to 10 actions executed in sequence (.atn files and inline ActionJSON can be mixed).

Font options rename

V1
V2
options.fonts[]
options.fontOptions.additionalFonts[] (capped at 10)
options.brushes[].href
options.brushes[].source.url
options.patterns[].href
options.patterns[].source.url

Stochastic filter validation caveats

Several Photoshop filter actions use an internal random seed that differs between V1 and V2 rendering engines. Pixel-diff comparison is not a valid acceptance test for payloads containing these filters — expect 50–80% pixel differences even when the ActionJSON is semantically identical and correctly migrated.

Known stochastic _obj values:

ActionJSON _obj
Filter Name
addNoise
Add Noise
grain
Grain
reticulation
Reticulation
mezzotint
Mezzotint
pointillize
Pointillize
clouds
Clouds
differenceClouds
Difference Clouds
spatter
Spatter
sprayedStrokes
Sprayed Strokes
colorHalftone
Color Halftone

Recommended validation alternatives for stochastic filters:

Removed V1 options

V1 Field
V2 Status
options.costOptimization
Removed — no equivalent
outputs[].includeMetadata
Removed — implicit
outputs[].embedICCProfiles
Replaced by outputs[].iccProfile object

Output field renames

V1
V2
quality (numeric 1–12)
quality (string enum: "very_poor", "poor", "low", "medium", "high", "maximum", "photoshop_max")
compression ("small"/"medium"/"large")
compression (10-level enum: "none" through "maximum")
overwrite
shouldOverwrite

Resource limits

Resource
V1
V2
Actions
1
10 (can mix .atn + inline)
Additional images/contents
25
25
Brushes
Undocumented
10
Patterns
Undocumented
10
Fonts
Undocumented
10
Outputs
Undocumented
25

End of LLM Migration Reference Guide