# Create Image

Registers a new image from a publicly accessible URL, optionally tagging it. The image is downloaded, validated against the supported formats and size limits, and processed asynchronously in the background.
## Authentication
Requires a valid API key in the `X-API-KEY` header.
## Request Body

```json
{
  "url": "https://example.com/images/product.jpg",
  "tags": ["ecommerce", "catalog", "summer-2024"]
}
```
### URL Field (Required)
The URL must meet the following requirements:
**Valid URL formats:**
- Must be a valid HTTP or HTTPS URL
- Must be absolute (e.g., `https://example.com/image.jpg`)
- Relative URLs are NOT supported (e.g., `/images/photo.jpg`)
- The filename will be automatically extracted from the URL path

**Examples of valid URLs:**

```
https://example.com/product.jpg
https://cdn.example.com/images/2024/photo.png
http://assets.example.com/catalog/item_123.gif
```
**Examples of invalid URLs:**

```
/images/product.jpg                    (relative URL)
example.com/image.jpg                   (missing protocol)
ftp://server.com/file.jpg              (FTP not supported)
https://example.com/page.html          (not a supported image extension)
https://example.com/product            (no extension to derive the file type from)
```
**URL validation rules:**
- Maximum length: 2048 characters
- The URL must be publicly accessible (no authentication supported)
- The host must resolve to a public address. URLs pointing at loopback, private ranges or the
cloud metadata endpoint are rejected
- The URL is probed with a `HEAD` request; anything other than a 2xx is rejected

### Tags Field (Optional)
Optional array of strings to categorize and organize your images.
**Rules:**
- Each tag cannot be empty or contain only whitespace
- Tags are case-sensitive
- Tags are stored as a single comma-separated string, so a tag containing a comma is read back as
several tags. Avoid commas inside a tag
- There is no explicit limit on the number of tags, but the whole comma-separated list must fit in
64 KB of storage

**Example:**

```json
{
  "url": "https://example.com/logo.png",
  "tags": ["branding", "corporate", "logo", "2024"]
}
```
## Supported Image Formats
The file type is taken from the URL's file extension, and the `Content-Type` header returned by the
URL is validated independently against the list of accepted image media types:
### Fully Supported Formats
| Format | Extension | Accepted Content-Type | Notes |
|  --- | --- | --- | --- |
| JPEG | .jpg, .jpeg | `image/jpeg` | Standard and progressive JPEG |
| PNG | .png | `image/png` | PNG-8, PNG-24, PNG-32 with transparency |
| GIF | .gif | `image/gif` | Static and animated GIF |
| TIFF | .tif, .tiff | `image/tiff`, `image/x-tiff` | Single and multi-page TIFF |
| WebP | .webp | `image/webp` | Lossy and lossless compression |
| PSD | .psd | `image/vnd.adobe.photoshop`, `image/photoshop`, `image/psd`, `application/octet-stream` | Adobe Photoshop files |

### Content-Type Validation
1. The URL **must return** a `Content-Type` header
2. The Content-Type **must be** one of the accepted media types listed above
3. `application/octet-stream` is **only accepted for .psd files** — many servers are misconfigured
and return this generic type for Photoshop files
4. Apart from that `application/octet-stream` rule, the Content-Type is **not** cross-checked
against the file extension: a URL ending in `.jpg` that serves `image/png` is accepted, and the
image is registered with file type `jpg`

**Examples of valid Content-Type responses:**

```
Content-Type: image/jpeg          → ✅ Accepted
Content-Type: image/png           → ✅ Accepted
Content-Type: image/gif           → ✅ Accepted
Content-Type: application/octet-stream with .psd extension → ✅ Accepted
```
**Examples of invalid Content-Type responses:**

```
Content-Type: text/html           → ❌ Rejected (not an image)
Content-Type: application/pdf     → ❌ Rejected (not supported)
Content-Type: application/octet-stream with .jpg extension → ❌ Rejected
(no Content-Type header)          → ❌ Rejected (missing header)
```
## Image Size and Weight Limits
The system automatically validates downloaded images:
### File Size Limits
- **Maximum file size:** 250 MB (262,144,000 bytes)
- Files larger than this limit will be rejected with a validation error

### Dimension Limits
- **Maximum width:** 30,000 pixels
- **Maximum height:** 30,000 pixels
- Images exceeding these dimensions will be rejected

## Response
### Success Response (201 Created)
Returns the reference (filename) of the created image, wrapped in the house entity envelope. The
reference is the public, tenant-unique key.

```json
{
  "value": {
    "reference": "product-123.jpg"
  },
  "@readLink": "https://api2.saleslayer.com/dam/images(product-123.jpg)",
  "@editLink": "https://api2.saleslayer.com/dam/images(product-123.jpg)"
}
```
A `Location` header points at the new resource, as an absolute URL —
`https://api2.saleslayer.com/dam/images(product-123.jpg)`.
The `@readLink`/`@editLink` annotations address the created image, matching the `Location` header,
so either can be followed to read or modify it. They are only emitted when hypermedia enrichment
is configured for the deployment; the `Location` header is always present.
The image will be processed asynchronously in the background: it is created with status `Up` and
the worker moves it to `Ok` or `Er`, filling in the dimensions and thumbnail URLs.
To check the processing status and retrieve full image details:

```
GET /images(product-123.jpg)
```
## Error Responses
### 400 Bad Request
Validation failures are returned in the shared envelope. Service-level messages carry no field
context, so they are grouped under a generic `error` key:

```json
{
  "validationFailures": {
    "error": ["URL is required"]
  }
}
```
The messages you can receive, verbatim:
| Cause | Message |
|  --- | --- |
| Missing or empty URL | `URL is required` |
| URL over 2048 characters | `URL exceeds maximum length of 2048 characters` |
| Not an absolute URL | `URL is not valid` |
| Scheme other than HTTP/HTTPS | `URL must use HTTP or HTTPS protocol` |
| No extension in the URL path | `Could not determine file type from URL. Please ensure the URL contains a filename with an extension.` |
| Unsupported extension | `File extension 'pdf' is not allowed. Allowed extensions: jpg, jpeg, png, gif, tif, tiff, psd, webp` |
| Host is not publicly routable | `URL host 'localhost' is not allowed.` |
| URL not reachable (non-2xx `HEAD`) | `URL is not accessible. HTTP status: 404 Not Found` |
| No `Content-Type` header | `URL does not return a valid Content-Type header` |
| `application/octet-stream` for a non-PSD file | `URL returns generic Content-Type (application/octet-stream) for non-PSD file. Extension detected: jpg` |
| Unsupported Content-Type | `URL does not return an image Content-Type. Received: text/html. Allowed types: image/jpeg, image/png, ...` |
| Empty file | `The uploaded file is empty` |
| File too large | `Image file size (300.00 MB) exceeds maximum allowed size of 250 MB` |
| Dimensions unreadable | `Could not read image dimensions from the uploaded file. Ensure it is a valid image in a supported format.` |
| Image too wide | `Image width (35000px) exceeds maximum allowed width of 30000px` |
| Image too tall | `Image height (35000px) exceeds maximum allowed height of 30000px` |
| Empty or whitespace-only tag | `Tags cannot contain empty values` |

### 401 Unauthorized
Missing or invalid API key in the `X-API-KEY` header. Emitted by the API gateway:

```json
{
  "message": "Unauthorized",
  "request_id": "d8aafa5b8f3e400b60bea0123dd33317"
}
```
Quote `request_id` when contacting support about a rejected request.
### 403 Forbidden
The API key does not have write permissions for this operation. Same body shape as the 401.
### 409 Conflict
Two distinct causes, both using the conflict envelope:
**An image with the same filename (`reference`) already exists for this tenant:**

```json
{
  "error": "An image with the filename 'product-123.jpg' already exists"
}
```
**The tenant has reached its image-library quota:**

```json
{
  "error": "Image library limit reached. Current: 5000, Maximum: 5000"
}
```
Images in status `Dv` (deleted) do not count towards the quota. Delete unused images to free
capacity, or contact Sales Layer to raise the limit.
### 500 Internal Server Error
Technical errors such as:
- Network connectivity issues reaching the source URL, or a download timeout (30s)
- Storage service unavailable
- Database errors

Note that a source URL that responds with a non-2xx status is a 400, not a 500 — only transport
failures and timeouts land here. The body identifies the request for support:

```json
{
  "error": "Please contact the administrator of this application by supplying the following code: 0HNF1A2B3C4D5"
}
```
## Complete Example
**Request:**

```http
POST /images HTTP/1.1
Host: api2.saleslayer.com
X-API-KEY: your-api-key-here
Content-Type: application/json
            
{
  "url": "https://cdn.example.com/products/summer-2024/product-123.jpg",
  "tags": ["ecommerce", "summer-collection", "featured", "new-arrival"]
}
```
**Success Response:**

```http
HTTP/1.1 201 Created
Content-Type: application/json
Location: https://api2.saleslayer.com/dam/images(product-123.jpg)
            
{
  "value": {
    "reference": "product-123.jpg"
  }
}
```
**Checking the result:**

```http
GET /images(product-123.jpg) HTTP/1.1
Host: api2.saleslayer.com
X-API-KEY: your-api-key-here
```
**Result after processing:**

```json
{
  "value": {
    "id": 42,
    "reference": "product-123.jpg",
    "status": "Ok",
    "fileType": "jpg",
    "width": 1920,
    "height": 1080,
    "sizeInBytes": 245760,
    "numLinks": 5,
    "tags": ["ecommerce", "summer-collection", "featured", "new-arrival"],
    "originalUrl": "https://cdn.example.com/product-123.jpg",
    "thumbnailUrl": "https://cdn.example.com/product-123_TH.jpg",
    "thumbnailMediumUrl": "https://cdn.example.com/product-123_THM.jpg",
    "thumbnailPreviewUrl": "https://cdn.example.com/product-123_THP.jpg",
    "createdOn": "2024-04-02T10:30:00",
    "modifiedOn": "2024-04-02T10:30:15"
  }
}
```

Endpoint: POST /images
Version: 2.0.0

## Header parameters:

  - `X-API-KEY` (string, required)
    Tenant's API key (required)

## Request fields (application/json):

  - `url` (string)
    Publicly accessible HTTP or HTTPS URL of the image to import. Required. Must be absolute and end
in a supported extension (jpg, jpeg, png, gif, tif, tiff, psd, webp); the image's `reference`
is the filename taken from the URL path. Maximum 2048 characters.

  - `tags` (array)
    Optional tags to associate with the image, e.g. `["product", "catalog", "2024"]`. Tags are
case-sensitive and cannot be empty or whitespace. Avoid commas inside a tag — tags are stored as a
comma-separated string and would be read back split.

## Response 201 fields (application/json):

  - `value` (object)
    The reference of a newly created image. Retrieve the full image with
`GET /images({reference})`; processing runs asynchronously, so the image starts in status
`Up` and moves to `Ok` or `Er`.

  - `value.reference` (string)
    The image's filename, unique per tenant. This is the public key used to address the image.

  - `@readLink` (string)
    Canonical URL of the created image, the same resource the `Location` header names.
Present only when hypermedia enrichment is enabled.

  - `@editLink` (string)
    URL to modify the created image, identical to `@readLink`. Present only when hypermedia
enrichment is enabled.

## Response 401 fields (application/json):

  - `message` (string)
    Error description, e.g. `Unauthorized`.

  - `request_id` (string)
    Gateway request identifier, to quote when contacting support.

## Response 403 fields (application/json):

  - `message` (string)

  - `request_id` (string)

## Response 409 fields (application/json):

  - `error` (string)

## Response 500 fields (application/json):

  - `error` (string)

  - `details` (any)

