<!-- BEGIN GENERATED API_HEADER -->

# PhotoDeck Backend API

Version: 1.1.2

Access your PhotoDeck media libraries and websites. Manage media metadata, collections and galleries, upload files, and monitor background work. Run AI metadata analyses and media exports, and search the Help Center.

<!-- END GENERATED API_HEADER -->

Send API requests to `https://api.photodeck.com`.
The paths in this guide are relative to that base URL and end in `.json`.

## Contents

- [Acceptable Use](#acceptable-use)
- [Glossary](#glossary)
- [Getting started](#getting-started)
- [Authentication](#authentication)
- [Request and response conventions](#request-and-response-conventions)
  - [Parameters and identifiers](#parameters-and-identifiers)
  - [Representations](#representations)
  - [Roles and access rights](#roles-and-access-rights)
  - [HTTP status codes and errors](#http-status-codes-and-errors)
  - [Pagination](#pagination)
  - [Background-job response links](#background-job-response-links)
  - [Rate limits, redirects and retries](#rate-limits-redirects-and-retries)
- [Endpoint reference](#endpoint-reference)
  - [Profile and disconnect](#profile-and-disconnect)
  - [Libraries](#libraries)
  - [Media](#media)
  - [Watermarks](#watermarks)
  - [Collections and smart collections](#collections-and-smart-collections)
  - [Media gateways](#media-gateways)
  - [Websites](#websites)
  - [Galleries](#galleries)
  - [Gallery display styles](#gallery-display-styles)
  - [Background jobs](#background-jobs)
  - [Documentation search](#documentation-search)
- [Uploading and replacing files](#uploading-and-replacing-files)
- [Client implementation checklist](#client-implementation-checklist)
- [Migrating from the legacy XML API](#migrating-from-the-legacy-xml-api)

## Acceptable Use

The PhotoDeck API helps you streamline routine tasks and automate repetitive work, within the normal use of your PhotoDeck account.

Access is provided on a fair-use basis, and we monitor API usage. PhotoDeck reserves the right to throttle, suspend, or revoke API keys, without prior notice in cases of clear abuse, for any account that violates these terms.

**You may not:**

- Circumvent, bypass, or manipulate account quotas, rate limits, or other usage restrictions

- Reimplement or replicate functionality reserved for higher tier plans or paid add-ons on top of a lower-tier plan (for example, building a multi-user system on a single-user plan)

- Use the API in ways inconsistent with its documented purpose or intended use cases

- Generate request volume or traffic disproportionate to normal usage patterns for your plan tier

- Resell, sublicense, rent, or otherwise grant third parties access to the API or your API key

- Build, operate, or support a product or service that directly competes with PhotoDeck’s core offering

- Scrape, cache, or persistently store API output beyond what your application needs to function

- Use API output, in whole or in part, to train, fine-tune, or otherwise develop machine learning or AI models

- Reverse-engineer, decompile, or attempt to extract the underlying code, models, or infrastructure behind the API

- Use the API for unlawful, fraudulent, or harmful purposes, or in violation of applicable laws or the PhotoDeck general Terms and Conditions

- Attempt to gain unauthorized access to PhotoDeck’s systems, other users’ accounts, or probe for vulnerabilities without authorization

Violations may result in rate-limiting, suspension, or termination of API access at PhotoDeck’s discretion.

## Glossary

| Term                   | Meaning                                                                                                                            |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| User                   | The account holder or team member authenticating to the backend. Not a visitor signing in to a customer website.                   |
| Account                | The subscription and ownership boundary for libraries and websites. Team members can have different permissions within an account. |
| Application            | A registered API client, identified by its OAuth client ID. Each user authorizes its access separately.                            |
| Media library / artist | A library of uploaded files. API paths and parameters call it `artists` and `artist_uuid`.                                         |
| Media                  | An image, raw image, video or document in a library. Its public identifier is `uuid`; API resource paths use the plural `medias`.  |
| Watermark              | A library's image/text watermark configuration.                                                                                    |
| Collection             | A manually managed group of media within one library. A media item can belong to several collections.                              |
| Smart collection       | A saved search within one library. Its contents are calculated from its matching criteria, not manually assigned.                  |
| Media group            | The common API resource for collections and smart collections: `medias_groups`.                                                    |
| Media gateway          | A library profile for sending media to an AI metadata assistant or exporting files to another service.                             |
| Pricing profile        | Media pricing/licensing settings.                                                                                                  |
| Website                | A hosted PhotoDeck site. Its `urlname` identifies it in nested API paths; `hostname` is its public domain.                         |
| Gallery                | A website tree node containing media and child galleries. Publication does not move originals out of their library.                |
| Root gallery           | The top of a website's gallery tree.                                                                                               |
| Gallery display style  | Website-specific presentation settings selected for a gallery.                                                                     |
| Lightbox               | A website-scoped selection of media.                                                                                               |
| Quotation / order      | Website sales resources.                                                                                                           |
| Background job         | Asynchronous work queued for best-effort execution.                                                                                |
| UUID                   | An opaque public identifier. Do not infer permissions from knowing a UUID.                                                         |

## Getting started

To make your first API calls:

1. Register your application and its redirect URIs through [Applications](https://my.photodeck.com/developer/applications).
   The application must be enabled by both its owner and PhotoDeck.

2. Implement the [OAuth authorization-code flow with PKCE](#connect-a-user). Users sign in to PhotoDeck in their browser, not in your application.

3. Send the resulting access token on every API request. `GET /whoami.json` returns the connected user's profile.

4. List `/artists.json` and `/websites.json` to discover accessible resources. Use website `urlname` values in nested website paths.

5. Fetch `/websites/{urlname}.json` for its `root_gallery_uuid`. List galleries or collections, or upload files as needed.

6. Follow pagination links and check HTTP status before parsing a response as a resource.

API availability and writable fields depend on the user's subscription, enabled features and resource permissions.
Only the endpoints in this guide are intended for API clients.

## Authentication

### OAuth endpoints and client registration

PhotoDeck uses the OAuth authorization-code flow with PKCE (`S256`), following the OAuth 2.1 draft. Users authorize your application in their browser,
so your application never needs their password or one-time codes.

The JSON API does not support password authentication, API request signatures or browser-session cookies.

The following paths use `https://my.photodeck.com`, not the API base URL:

| Method and path                               | Behavior                                                              |
| --------------------------------------------- | --------------------------------------------------------------------- |
| `GET /.well-known/oauth-authorization-server` | Discover the issuer, endpoints and supported capabilities.            |
| `GET /oauth/authorize`                        | Open in the system browser for sign-in and authorization.             |
| `POST /oauth/token`                           | Exchange a code or refresh a token. Use a form-encoded body.          |
| `POST /oauth/revoke`                          | Disconnect using an access or refresh token. Use a form-encoded body. |

Register your application and redirect URIs before connecting users; dynamic client registration is not supported.

Use the client ID shown in your application settings. Hosted integrations using an HTTPS Client ID Metadata Document (CIMD) URL
as their client ID require prior approval from PhotoDeck. Their redirect URIs and application name are configured automatically from
the metadata document, along with the application URL (`client_uri`) and logo URL (`logo_uri`) when provided.

Each user can have a limited number of active connections to an application. A new connection revokes the oldest
connections as needed to stay within this limit.

Desktop and JavaScript-only browser applications are **public clients**: use the client ID without a secret.
Do not embed a shared secret in a distributed plugin or browser application.

Server-side applications can generate a separate OAuth client secret in their application settings. Such confidential clients must
authenticate at the token and revocation endpoints using `client_secret_basic` or `client_secret_post`.

With Basic authentication, an optional body `client_id` must match the Basic username; do not also send a body `client_secret`.
The legacy XML API secret is separate and cannot be used as an OAuth client secret.

HTTPS callbacks must match a registered URI exactly. Native clients can use registered loopback callbacks such as
`http://127.0.0.1/callback`, with an ephemeral port. The host, path and query must still match; `localhost`, `127.0.0.1` and `[::1]`
are separate registered hosts. Open the system browser rather than an embedded sign-in form.

### Connect a user

Connecting a user takes four steps: prepare PKCE, open authorization, check the callback, and exchange the code for tokens.

1. Generate a cryptographically random PKCE `code_verifier` of 43–128 URL-safe characters and an unpredictable `state`.
   Compute `code_challenge = BASE64URL(SHA256(code_verifier))`, without padding. Keep the verifier and state private until the callback.

2. Open `https://my.photodeck.com/oauth/authorize` with these query parameters:

   | Parameter               | Required    | Description                                                                     |
   | ----------------------- | ----------- | ------------------------------------------------------------------------------- |
   | `response_type`         | Yes         | `code`.                                                                         |
   | `client_id`             | Yes         | Your registered client ID.                                                      |
   | `redirect_uri`          | Yes         | Your registered callback URI.                                                   |
   | `code_challenge_method` | Yes         | `S256`.                                                                         |
   | `code_challenge`        | Yes         | PKCE challenge calculated from `code_verifier`.                                 |
   | `scope`                 | No          | `api` (default).                                                                |
   | `resource`              | No          | `https://api.photodeck.com` (default).                                          |
   | `state`                 | Recommended | Your unpredictable value; send and verify it to protect the authorization flow. |

3. The user signs in to PhotoDeck, including two-factor authentication when enabled, and authorizes your application.
   The callback contains `code`, the original `state`, and `iss=https://my.photodeck.com`.
   Verify state and issuer before exchanging the code. Handle `error=access_denied` when the user cancels.

4. Exchange the code promptly at `POST https://my.photodeck.com/oauth/token`, using these form-encoded parameters:

   | Parameter       | Required           | Description                                                                                                               |
   | --------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------- |
   | `grant_type`    | Yes                | `authorization_code`.                                                                                                     |
   | `client_id`     | Without Basic auth | Your registered client ID; confidential clients can use [Basic authentication](#oauth-endpoints-and-client-registration). |
   | `code`          | Yes                | Authorization code from the callback.                                                                                     |
   | `redirect_uri`  | No                 | May be omitted with PKCE. If supplied, must match the callback URI used in the authorization request exactly.             |
   | `code_verifier` | Yes                | Your private PKCE verifier.                                                                                               |
   | `resource`      | No                 | `https://api.photodeck.com` (default).                                                                                    |

URL-encode each value. Codes are single-use. PKCE is required for confidential clients too, unless PhotoDeck explicitly approves
a legacy OAuth 2.0 exception. Exempt clients exchanging codes issued without PKCE must send the original `redirect_uri`
and must not send `code_verifier`; these exchanges do not follow the OAuth 2.1 draft.

If a code expires or is lost before exchange, start browser authorization again. Reusing a redeemed code revokes its connection.

A successful exchange returns these JSON fields:

| Field           | Description                               |
| --------------- | ----------------------------------------- |
| `access_token`  | Opaque access token.                      |
| `token_type`    | `Bearer`.                                 |
| `expires_in`    | Integer access-token lifetime in seconds. |
| `refresh_token` | Opaque refresh token.                     |
| `scope`         | `api`.                                    |

The `api` scope gives the application the authorizing user's API access. Access follows changes to that user's library and website roles.

To limit an integration's access, authorize it as a team member with the appropriate library and website roles (available on selected plans).
The account holder can select a team member when authorizing a connection on their behalf.

Users can review and disconnect applications under [Connected apps](https://my.photodeck.com/connected_apps).

### JavaScript browser clients (CORS)

OAuth token, revocation and discovery endpoints, the JSON API and MCP support CORS over HTTPS.
Use `credentials: "omit"` with fetch and bearer authentication for API calls. Open the authorization URL in the browser, not through fetch.

API response headers, including `Link`, `Location` and rate-limit information, are readable by JavaScript.
MCP also requires a [registered origin](MCP.md#request-origins).

### Call the API, refresh and disconnect

Send `Authorization: Bearer ACCESS_TOKEN` on **every** JSON API request. Do not put tokens in query strings or rely on a cookie jar.

Treat tokens as opaque values: store and send them without trying to interpret their contents.
An invalid, missing, expired or revoked access token returns `401` with a `WWW-Authenticate: Bearer` challenge.
Protected-resource metadata is available at `https://api.photodeck.com/.well-known/oauth-protected-resource`.

#### Refreshing tokens

Use the `expires_in` value returned by each code exchange or refresh to schedule renewal; do not hardcode token lifetimes.
Refresh at `/oauth/token` with these form-encoded parameters:

| Parameter       | Required           | Description                                                                                 |
| --------------- | ------------------ | ------------------------------------------------------------------------------------------- |
| `grant_type`    | Yes                | `refresh_token`.                                                                            |
| `client_id`     | Without Basic auth | Your registered client ID; confidential clients can supply it through Basic authentication. |
| `refresh_token` | Yes                | Current refresh token.                                                                      |
| `resource`      | No                 | Must remain within the original authorization.                                              |

After a refresh, save **both** replacement tokens together (atomically). The previous access and refresh tokens are no longer valid.
Allow only one refresh attempt at a time for each connection.

Reusing an already consumed refresh token revokes that connection. If a network failure leaves you unsure whether a refresh succeeded,
do not retry the exchange blindly.

Refresh tokens can expire or be revoked; do not assume a fixed validity period.
Password/OTP changes, user deactivation, application disabling or user-initiated revocation can also invalidate credentials.

If refresh returns `invalid_grant`, ask the user to reconnect in their browser.
OAuth endpoint errors use the standard `error` and `error_description` fields, rather than the [resource API's error format](#http-status-codes-and-errors).

#### Disconnecting

To disconnect, POST these form-encoded parameters to `/oauth/revoke`:

| Parameter   | Required           | Description                                                                                 |
| ----------- | ------------------ | ------------------------------------------------------------------------------------------- |
| `client_id` | Without Basic auth | Your registered client ID; confidential clients can supply it through Basic authentication. |
| `token`     | Yes                | Access or refresh token; either revokes the whole connection.                               |

An unknown token returns `200` without revealing whether it previously existed.
Store tokens in secure credential storage and never send them to storage-upload hosts.

### Bearer curl example

The Bash examples use `curl` 7.82 or later. Set `ACCESS_TOKEN` securely after completing OAuth:

```bash
base_url='https://api.photodeck.com'

api_curl() {
  local method=$1 target=$2
  shift 2
  curl --silent --show-error --fail-with-body --request "$method" \
    --header "Authorization: Bearer $ACCESS_TOKEN" \
    "$base_url$target" "$@"
}

api_curl GET '/whoami.json'
api_curl GET '/artists.json?per_page=1'
```

This helper does not implement refresh, redirects or rate-limit retries. Handle those as described in this guide.

## Request and response conventions

### Parameters and identifiers

- Send GET parameters in the query string. URL-encode names and values, including brackets, spaces and non-ASCII characters.
  Encode a literal `+` as `%2B`; an unescaped `+` is interpreted as a space.

- To create, update or delete resources, send a JSON object as the request body with `Content-Type: application/json`.
  Include the documented resource object, such as `gallery` or `media`; do not omit this nesting.

- Use UTF-8 strings. Do not rely on preservation of four-byte Unicode characters such as emoji.

- Use UUID-valued relationship fields ending in `_uuid` or `_uuids`. Website path placeholders use the returned `urlname` instead.

- Use JSON booleans (`true`/`false`) for switches and numbers for numeric fields. In query strings, booleans accept `0`/`1` or `true`/`false`.
  Response booleans are JSON booleans.

- Treat date/time response values as ISO 8601 strings and allow `null` for unset values. Counts and dimensions are numbers when present.

- Updates change only the attributes you supply; they do not replace the entire resource.
  For relationship replacement arrays, see [Updating media](#put-mediasuuidjson).
  Omit a field to leave it unchanged. Do not use `null` as a general-purpose way to clear fields; use the endpoint's documented clearing operation.

Parameter tables use bracket notation to describe nesting. In a JSON body, use nested objects and arrays, not literal bracketed keys:

| Parameter notation                  | JSON body                                                           |
| ----------------------------------- | ------------------------------------------------------------------- |
| `media[title]`                      | `{ "media": { "title": "Sunrise" } }`                               |
| `media[publish_to_gallery_uuids][]` | `{ "media": { "publish_to_gallery_uuids": ["UUID_1", "UUID_2"] } }` |
| `media[content][file_size]`         | `{ "media": { "content": { "file_size": 1234 } } }`                 |

In query strings, repeat array fields with `[]` and URL-encode the brackets.
For example, `filter[medias_uuid][]=UUID_1` becomes `filter%5Bmedias_uuid%5D%5B%5D=UUID_1` in a GET URL.

For custom-metadata tag filters, repeat `filter[custom_tags][METADATA_UUID][]=TAG_UUID` for each tag, URL-encoding the bracketed key.

GET requests do not use JSON bodies.

### Representations

List endpoints return a JSON array of basic objects, without a `data` or resource-name wrapper.
These lists omit detailed metadata and relationships; use a detail endpoint when you need more information.

In the endpoint reference, response-field tables list the basic fields and mark additional detail fields as **Details only**,
unless noted otherwise.

Resource details, creation and synchronous updates return a JSON object, also without a wrapper. Deletions return an empty body.

For example, the library list returns an array like this (other fields omitted):

```json
[
  {
    "uuid": "LIBRARY_UUID",
    "name": "Main library",
    "color": "#ff9900"
  }
]
```

Where documented, request optional data with `include`, using an array of names.
For JSON bodies, use `"include": ["association_name"]`. For GET requests, repeat `include[]=association_name` in the query string and URL-encode the brackets.
Unsupported inclusions on those detail endpoints return `400`; list endpoints ignore `include`.

Relationship arrays contain only resources the user can access, and an included UUID array can be empty.

Clients should ignore unknown response fields, tolerate nullable values, and avoid depending on JSON object key order.

Do not post an entire returned object back to an endpoint: send only intended writable fields.
Fields you lack permission to change may be ignored. Check the returned resource to confirm important changes.

### Roles and access rights

Role names in this guide match the team-member settings and the Help Center’s “Adding and managing team members” article.
Rights are assigned separately for each media library and website; they are not a hierarchy.

The account holder has all rights within their account, subject to subscription features.
These are the same resource permissions used by the Web UI, not a separate set of API roles.

- **Media-library rights:** **Base**, **Write**, **Delete**, **Collections administration**, **General administration**.
  **Base** is the access granted by membership in a library; the other rights add specific capabilities.

- **Website rights:** **Website media**, **Galleries**, **Users and orders**, **Business administration**, **Custom pages**, **Website customization**.
  “Any website role” means any of these rights on the relevant website, not unrestricted access to every website operation.

Website roles do not grant library **Write**/**Delete** rights. Likewise, **Galleries** does not imply **Website media**.
The [endpoint reference](#endpoint-reference) lists the required roles for each operation.

### HTTP status codes and errors

Check the HTTP status before using the response body. These conventions apply throughout the reference;
any endpoint-specific exceptions are noted in that endpoint's section.

| Status                     | Meaning and client behavior                                                                                                       |
| -------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `200 OK`                   | Successful read or synchronous update. Parse the array, resource object or documented notice.                                     |
| `201 Created`              | Created resource details, with a relative `.json` `Location`. Resolve it against the API base URL.                                |
| `202 Accepted`             | Batch work queued, with a `notice`, not completed.                                                                                |
| `204 No Content`           | Successful deletion or logout. The body is empty: do not JSON-decode it.                                                          |
| `307 Temporary Redirect`   | Retry the same method and body at a trusted `Location`, retaining bearer authentication. See retry handling.                      |
| `400 Bad Request`          | Invalid request, including an unsupported association inclusion. Correct it before retrying.                                      |
| `401 Unauthorized`         | Access token missing, expired or invalid. Refresh it, or reconnect through browser authorization if refresh fails.                |
| `403 Forbidden`            | Access/subscription/feature checks denied the operation. Inspect the error, not just the status.                                  |
| `404 Not Found`            | Unknown or inaccessible resource, or endpoint unavailable on the API host. An inaccessible resource may deliberately look absent. |
| `422 Unprocessable Entity` | Validation or mutation failed, including some mutation-permission failures. Not every permission failure is `403`.                |
| `429 Too Many Requests`    | Throttled. Wait according to `Retry-After`.                                                                                       |
| `5xx`                      | Server/upstream failure. The body need not be JSON; apply bounded retries only when safe.                                         |

API-generated errors normally have this shape:

```json
{ "error": "A displayable error message" }
```

Some responses also contain `errors`, an object of field names to arrays of validation messages.

Notices use `{ "notice": "A displayable message" }`. Message text can be localized and is not a stable machine-readable error code.

Proxies and rate limiters may return other content types. Always inspect HTTP status and content type before decoding.
Treat messages as text, not trusted HTML.

Keep the `X-Request-ID` response header to help with debugging or support requests, but do not log credentials or sensitive payloads.

### Pagination

List endpoints return results a page at a time. You can request a page and page size with these query parameters;
the server limits the page size to the endpoint's maximum.
A page beyond the end returns an empty array.

| Parameter  | Required | Description                                            |
| ---------- | -------- | ------------------------------------------------------ |
| `page`     | No       | Page number, starting at 1.                            |
| `per_page` | No       | Requested page size, capped at the endpoint's maximum. |

Omit `per_page` to use the server’s default. The server can return fewer items than requested;
page sizes can vary by endpoint and change over time.

To find the next page, use the HTTP `Link` response header:

```text
Link: <https://api.photodeck.com/medias.json?page=2>; rel="next"
```

Follow the URL with `rel="next"` **unchanged**, retaining all filters and other parameters. Send bearer authentication on each request.

Stop when there is no next link. Do not build page URLs yourself, rely on fixed page sizes, or infer the end from the number of items returned.
Do not follow links to unexpected hosts with bearer credentials.

`prev` links and `X-Total-Count`, `X-Page`, `X-Per-Page`, `X-Total-Pages` and `Link-Template` headers may provide additional information.
Treat them as optional: your client must be able to follow all pages without relying on counts, page numbers or a link template.
`Link-Template` has `rel="collection"` and a `{page}` variable; it is not itself the next-page URL.

Lists can change while you read them: pagination does not provide a fixed snapshot.
When synchronizing changing data, deduplicate results by UUID.

### Background-job response links

When a request queues one or more background jobs, its response includes a `Link` with the standard `rel="monitor"` relation:

```text
Link: </jobs/requests/REQUEST_ID.json>; rel="monitor"
```

This applies to synchronous responses that also queue work, not just `202` responses.

Resolve relative links against the API base URL, follow them unchanged and authenticate each lookup with your bearer token.
The header can contain other relations, such as pagination's `next`; select the link by its relation rather than assuming a single link.

See [Background jobs](#background-jobs) for job visibility, status and polling guidance.

### Rate limits, redirects and retries

- Reuse HTTP connections and keep API traffic at or below one request per second unless the returned rate-limit headers permit otherwise.
  Observe `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset` when provided.

- On `429`, honor `Retry-After`; do not immediately retry in parallel.

- The service can return `307` to ask your client to retry the request. Keep the same HTTP method and body, forward bearer credentials
  only to trusted API destinations, and limit the number of redirects.

- For temporary read failures, increase the delay between retries and add a random variation (exponential backoff with jitter).
  Limit the number of attempts. Do not endlessly retry `400`, `401`, `403`, `404` or `422`.

- Do not blindly repeat a create, update or delete request after a timeout: the first request may already have succeeded.
  There is no supported idempotency-key parameter.
  Check the affected resources before retrying creations, uploads or queued operations. A `307` retry is distinct from an ambiguous network failure.

## Endpoint reference

<!-- BEGIN GENERATED API_REFERENCE -->

All endpoints require an OAuth bearer token. Additional access and subscription requirements are noted below.

Paths use `{website}` for a website’s `urlname`, `{artist_uuid}` for a library UUID and `{uuid}` for a resource UUID.

List endpoints use [pagination](#pagination). Parameters below are optional unless marked required; send nested JSON objects for request bodies and bracketed keys for query strings.

See [response conventions](#representations), [errors](#http-status-codes-and-errors) and the [OpenAPI specification](openapi.json) for the complete request and response schemas.

### Profile and disconnect

Identify the connected backend user and disconnect their OAuth connection.

**Response fields:**

| Field | Description |
| --- | --- |
| `email` | Connected user’s email address. |
| `firstname` | First name. |
| `lastname` | Last name. |
| `name` | Full name. |
| `preferred_locale` | Preferred locale; use it for documentation searches when supported. |

#### `GET /whoami.json`

Get the connected user’s profile.

Returns the connected backend user’s profile, including preferred_locale, not a customer-website visitor’s profile.

**Success:** `200` — JSON object.

#### `GET /logout.json`

Revoke the current OAuth connection.

Returns an empty body. Prefer POST /oauth/revoke for disconnecting.

**Success:** `204` — The current OAuth connection was revoked.

### Libraries

Start with the library list to discover accessible libraries and their roles. Being able to see a library does not grant every operation.

Libraries are read-only through this API. Roles do not override subscription features or operation-specific requirements.
Role objects contain an English `name` and a boolean `granted`; rights are independent, not a hierarchy.

**Response fields:**

| Field | Description |
| --- | --- |
| `uuid` | Library UUID. |
| `name` | Library name. |
| `color` | Library color, for example #ff9900. |
| `roles` | Connected user’s media-library roles. |
| `roles[r_base]` | Base. |
| `roles[r_m_write]` | Write. |
| `roles[r_m_delete]` | Delete. |
| `roles[r_mg_admin]` | Collections administration. |
| `roles[r_admin]` | General administration. |

#### `GET /artists.json`

List accessible media libraries and the connected user’s roles.

Returns a paginated array of basic library objects. Use the returned UUIDs to scope media requests.

Required roles: **Base** on each returned library.

An active subscription is required.

**Success:** `200` — Array of basic objects.

### Media

Find media, read or update metadata, and manage gallery/collection relationships.
File uploads require requesting instructions, transferring bytes to storage and confirming completion.

**Response fields:**

| Field | Description |
| --- | --- |
| `uuid` | Public UUID. |
| `type` | Media type. Values: `Image`, `RawImage`, `Video`, `Document`. |
| `title` | Media title. |
| `alt_text` | Alternative text. |
| `width` | Width; may be unset for unprocessed files or non-image content. |
| `height` | Height; may be unset for unprocessed files or non-image content. |
| `processed` | Whether file processing is complete. |
| `original_created_at` | Original content creation date/time. |
| `created_at`, `updated_at` | ISO 8601 date/time, or null when unset. |
| `file_name` | Returned filename; can differ from the submitted filename. Use it for upload transfer/confirmation. |
| `mime_type` | Returned MIME type; can differ from the submitted MIME type. Use it for upload transfer/confirmation. |
| `file_size` | File size in bytes. |
| `file_updated_at` | File last-update date/time. |
| `artist_rating` | Library rating; 0 or null when unrated. Range: 0–5. |
| `life_cycle_state` | Lifecycle state, normally unknown, final or proof. |
| `expires_at` | Expiry date/time. |
| `image_urls` | Maximum image sizes in pixels mapped to HTTPS thumbnail URLs, e.g. `{"175":"https://example.com/thumbnail.jpg"}`. Sizes may change; URLs may show a placeholder while processing or when unavailable. No original-file download URL is returned. |
| `description` | **Details only.** Media caption or description. |
| `keywords` | **Details only.** Keyword list. Unlike the comma-separated write parameter, this response field is an array. |
| `location` | **Details only.** Location within the city. |
| `city` | **Details only.** City. |
| `state` | **Details only.** State or province. |
| `region` | **Details only.** Region. |
| `country` | **Details only.** Country. |
| `author` | **Details only.** Author. |
| `copyright` | **Details only.** Copyright notice. |
| `model_release` | **Details only.** Whether a model release is present. |
| `property_release` | **Details only.** Whether a property release is present. |
| `pricing_profile_uuids`, `gallery_uuids`, `medias_collection_uuids` | **Details only.** Accessible related UUIDs. Present only with the corresponding include; may be empty. |
| `custom_metadata` | **Details only.** Present only with `custom_metadata` in include. Field UUIDs mapped to text, a tag UUID or a tag UUID array. Empty metadata returns {}. |

#### `GET /medias.json`

List accessible photos, videos and documents.

Returns basic metadata and thumbnails. The same scopes apply to individual media operations.

Optionally supply at most one top-level scope: `artist_uuid`, `gallery_uuid`, `gallery_subtree_uuid`, `medias_group_uuid`, `lightbox_uuid`, `website_order_uuid`, `website_quotation_uuid`.

Invalid or blank filters may be ignored: verify the selection before a destructive batch.

Required roles: **Base** on the library unless scoped access permits otherwise:

- Galleries: **Website media** permits media from libraries with **Base**; **Galleries** permits all gallery media.
- Orders: **Base** permits media from accessible libraries; **Users and orders** permits all order media.

Read access does not grant **Write** or **Delete**.

An active subscription is required.

**Scope:**

| Parameter | Value | Description |
| --- | --- | --- |
| `artist_uuid` | UUID | Optional media-library scope; requires **Base**. |
| `gallery_uuid` | UUID | Optional direct-gallery scope; requires **Website media** or **Galleries**. |
| `gallery_subtree_uuid` | UUID | Optional gallery and descendant scope; requires **Website media** or **Galleries**. |
| `medias_group_uuid` | UUID | Optional collection/smart-collection scope; requires **Base**. |
| `lightbox_uuid` | UUID | Optional lightbox scope; requires **Users and orders**. |
| `website_order_uuid` | UUID | Optional order scope; **Base** limits results to accessible libraries; **Users and orders** permits all order media. |
| `website_quotation_uuid` | UUID | Optional quotation scope; requires **Users and orders**. |

**Search filters:**

| Parameter | Value | Description |
| --- | --- | --- |
| `filter[search]` | text | Text query supporting quoted phrases and +required/-excluded terms. |
| `filter[search_locale]` | text | Supported website locale for search. |
| `filter[with_keywords]` | text | Keyword query with quoted phrases and +/- terms; `*` means any keywords, `!` means none. |
| `filter[with_keywords_locale]` | text | Supported website locale for with_keywords. |
| `filter[media_type]` | text | Media type. Values: `Image`, `RawImage`, `Video`, `Document`. |
| `filter[orientation]` | text | Image orientation. Values: `vertical`, `square`, `horizontal`, `panoramic`. |
| `filter[author]` | text | Author to match. |
| `filter[life_cycle_state]` | text | Configured lifecycle-state value. |
| `filter[model_release]` | `true` / `false` | Whether a model release is present. |
| `filter[property_release]` | `true` / `false` | Whether a property release is present. |
| `filter[published]` | `true` / `false` or UUID | True for published, false for unpublished, or a website UUID to match publication on that website. |
| `filter[priced]` | `true` / `false` | Whether media have a pricing profile. |
| `filter[with_title]` | `true` / `false` | Whether media have a title. |
| `filter[ready_for_sale]` | `true` / `false` | Whether media are ready for sale. |
| `filter[with_comments]` | `true` / `false` | Whether media have comments. |
| `filter[pricing_profile]` | UUID | Pricing-profile UUID. |
| `filter[min_mpixel]` | integer | Minimum whole-number megapixels. At least 1. |
| `filter[max_mpixel]` | integer | Maximum whole-number megapixels. At least 1. |
| `filter[min_artist_rating]` | integer | Minimum library rating, inclusive. At least 1. |
| `filter[max_artist_rating]` | integer | Maximum library rating, inclusive. At least 1. |
| `filter[min_visitor_rating]` | integer | Minimum visitor rating. At least 1. |
| `filter[max_visitor_rating]` | integer | Maximum visitor rating. At least 1. |
| `filter[uploaded_within]` | integer | Uploaded within this many days. At least 1. |
| `filter[created_min]` | text | Earliest creation date, e.g. 2026-10-15. |
| `filter[created_max]` | text | Latest creation date, including that day, e.g. 2026-10-15. |
| `filter[expires_at_min]` | text | Earliest expiry date. Availability depends on the subscribed plan. |
| `filter[expires_at_max]` | text | Latest expiry date, including that day. Availability depends on the subscribed plan. |
| `filter[medias_uuid][]` | UUID list | Media UUIDs to match. |
| `filter[custom_tags]` | object | Tag UUID arrays by metadata field UUID. |

**Success:** `200` — Array of basic objects.

#### `GET /medias/{uuid}.json`

Get detailed media metadata and thumbnails.

Request relationships and custom metadata with the `include` array; otherwise they are omitted.
Optionally supply at most one top-level scope: `artist_uuid`, `gallery_uuid`, `gallery_subtree_uuid`, `medias_group_uuid`, `lightbox_uuid`, `website_order_uuid`, `website_quotation_uuid`.

Required roles: **Base** on the library unless scoped access permits otherwise:

- Galleries: **Website media** permits media from libraries with **Base**; **Galleries** permits all gallery media.
- Orders: **Base** permits media from accessible libraries; **Users and orders** permits all order media.

Read access does not grant **Write** or **Delete**.

An active subscription is required.

**Scope:** See [scope](#get-mediasjson).

**Optional details:**

| Parameter | Value | Description |
| --- | --- | --- |
| `include[]` | text list | Optional data to return. Relationship arrays contain only accessible UUIDs. Custom metadata maps field UUIDs to text, a tag UUID or a tag UUID array; empty metadata returns {}. Unsupported names return 400. Values: `pricing_profile_uuids`, `gallery_uuids`, `medias_collection_uuids`, `custom_metadata`. |

**Success:** `200` — JSON object.

#### `POST /medias.json`

Request instructions to upload a new media file or replace a filename match.

Supply file information under `media.content` and optional metadata under `media`.

Set `replace` to true under `media` for filename replacement.

Replacement can delete previous file versions before transfer completes, even if the transfer fails.

Transfer the original bytes using the returned upload instructions, then confirm completion.

Required roles: **Write** on the destination library; **Delete** as well for filename replacement. Without **Delete**, the replace flag may be ignored and a new media item created.

An active subscription is required.

**Upload options:**

| Parameter | Value | Description |
| --- | --- | --- |
| `artist_uuid` | UUID | Destination library UUID; specify it for predictable selection. |
| `media[add_to_medias_collection_uuids][]` | UUID list | Add regular-collection memberships; requires library **Write**. |
| `media[publish_to_gallery_uuids][]` | UUID list | Publish to galleries; requires **Base** and **Website media**. |
| `media[watermark_uuid]` | text | Library watermark UUID, or none to disable watermarking. Omit to use the default on new media or preserve the existing choice. |
| `media[replace]` | `true` / `false` | Replace an existing filename match, or create if absent. Requires library **Delete** as well as **Write**. |

**Optional details:** See [optional details](#get-mediasuuidjson).

Example: **new upload**.

```json
{
  "artist_uuid": "00000000-0000-0000-0000-000000000001",
  "media": {
    "content": {
      "upload_location": "REQUEST",
      "file_name": "IMG1234.JPG",
      "file_size": 1234,
      "mime_type": "image/jpeg"
    }
  }
}
```

**Success:** `201` — New media with upload instructions when the request fields were accepted.

**Success:** `200` — Existing filename match replaced; retains its UUID.

#### `PUT /medias/{uuid}.json`

Update media or request/confirm a replacement upload.

Metadata, relationship and upload operations share this endpoint.

Send changes under `media`; omitted fields are preserved. An empty string clears a text field.
Optionally supply at most one top-level scope: `artist_uuid`, `gallery_uuid`, `gallery_subtree_uuid`, `medias_group_uuid`, `lightbox_uuid`, `website_order_uuid`, `website_quotation_uuid`.

Prefer incremental relationship changes: visible memberships may be incomplete. An empty replacement array clears memberships.

File replacement requires requesting upload instructions, transferring bytes to storage and confirming completion.

- A replacement request may delete previous file versions before transfer completes.
- Confirmation starts asynchronous processing, not immediate completion.
- If `stop_with_error` is true in a failure, stop the upload queue.

Required roles: **Write** for metadata; read access through the selected scope. Relationship rights are separate:

- Gallery publication/pricing: **Base** and **Website media**, not **Business administration** alone.
- Lightboxes/quotations: **Users and orders**, not library **Base** or **Write**. Quotations must be editable.

Uploads: **Write** on the library; replacing a processed file also requires **Delete**.

An active subscription is required.

**Metadata:**

| Parameter | Value | Description |
| --- | --- | --- |
| `media[title]` | text | Media title. |
| `media[description]` | text | Media caption or description. |
| `media[alt_text]` | text | Alternative text for accessibility. |
| `media[keywords]` | text | Comma-separated keywords, replacing existing keywords. |
| `media[add_keywords]` | text | Comma-separated keywords to add without replacing existing keywords. |
| `media[remove_keywords]` | text | Comma-separated keywords to remove. |
| `media[location]` | text | Location within the city. |
| `media[city]` | text | City. |
| `media[state]` | text | State or province. |
| `media[region]` | text | Region. |
| `media[country]` | text | Country. |
| `media[author]` | text | Author. |
| `media[copyright]` | text | Copyright notice. |
| `media[model_release]` | `true` / `false` | Whether a model release is present. |
| `media[property_release]` | `true` / `false` | Whether a property release is present. |
| `media[original_created_at]` | text | Original content creation date/time, e.g. 2026-10-15T14:30:00Z; empty to clear. |
| `media[life_cycle_state]` | text | Configured lifecycle-state value. |
| `media[artist_rating]` | integer | Library rating in stars. Range: 1–5. |
| `media[delete_artist_rating]` | `true` / `false` | Set true to clear the library rating. |
| `media[expires_at]` | text | Expiry date/time; empty to clear. Availability depends on the subscribed plan. |
| `media[custom_metadata]` | object | Availability depends on the subscribed plan. Map metadata field UUIDs to text, a tag UUID or an array of tag UUIDs. For multiple tags, values add to existing tags; include "_destroy" to replace them. Use empty text or null to clear text/single-tag fields; ["_destroy"] clears a multiple-tag field. |

**Relationships:**

| Parameter | Value | Description |
| --- | --- | --- |
| `media[artist_uuid]` | UUID | Destination library UUID; moving requires **Delete** on the source and **Write** on the destination. Moving removes collection memberships; assign destination collections in a separate request. |
| `media[medias_collection_uuids][]` | UUID list | Replace regular-collection memberships; requires library **Write**. |
| `media[add_to_medias_collection_uuids][]` | UUID list | Add regular-collection memberships; requires library **Write**. |
| `media[remove_from_medias_collection_uuids][]` | UUID list | Remove regular-collection memberships; requires library **Write**. |
| `media[gallery_uuids][]` | UUID list | Replace gallery publications within the authorized scope; requires **Base** and **Website media**. |
| `media[publish_to_gallery_uuids][]` | UUID list | Publish to galleries; requires **Base** and **Website media**. |
| `media[unpublish_from_gallery_uuids][]` | UUID list | Unpublish from galleries; requires **Base** and **Website media**. |
| `media[add_pricing_profile_uuids][]` | UUID list | Add pricing profiles; requires **Base** and **Website media**. |
| `media[remove_pricing_profile_uuids][]` | UUID list | Remove pricing profiles; requires **Base** and **Website media**. |
| `media[pricing_profile_uuids][]` | UUID list | Replace pricing profiles; restricted to account holders. |
| `media[add_to_lightbox_uuid]` | UUID | Add to an accessible lightbox; requires **Users and orders**. |
| `media[remove_from_lightbox_uuid]` | UUID | Remove all occurrences from an accessible lightbox, regardless of source gallery; requires **Users and orders**. |
| `media[add_to_quotation_uuid]` | UUID | Add to an editable quotation; requires **Users and orders**. |
| `media[remove_from_quotation_uuid]` | UUID | Remove from an editable quotation; requires **Users and orders**. |

**Scope:** See [scope](#get-mediasjson).

**Optional details:** See [optional details](#get-mediasuuidjson).

Example: **update metadata**.

```json
{
  "artist_uuid": "00000000-0000-0000-0000-000000000001",
  "media": {
    "title": "Sunrise"
  }
}
```

**Success:** `200` — JSON object.

#### `DELETE /medias/{uuid}.json`

Permanently delete one media item and its original file.

This is permanent deletion, not merely unpublishing from a gallery.

Optionally supply at most one top-level scope: `artist_uuid`, `gallery_uuid`, `gallery_subtree_uuid`, `medias_group_uuid`, `lightbox_uuid`, `website_order_uuid`, `website_quotation_uuid`.

Required roles: **Delete** on the library, and read access through the selected scope.

An active subscription is required.

**Scope:** See [scope](#get-mediasjson).

Example: **library scope**.

```json
{
  "artist_uuid": "00000000-0000-0000-0000-000000000001"
}
```

**Success:** `204` — Media permanently deleted.

#### `PATCH /medias.json`

Queue equivalent changes to a media selection.

Supply metadata and relationship changes under `medias`. Batch-specific behavior:

- Blank strings, null, empty arrays and inactive command switches are ignored; empty arrays do not clear memberships.
- Rating 0 is ignored; use `delete_artist_rating` to clear it.
- Library moves discard collection assignments; assign them afterwards.
- `delete` permanently deletes selected originals.

Set `select` to comma-separated media UUIDs or `all`.
Optionally supply at most one top-level scope: `artist_uuid`, `gallery_uuid`, `gallery_subtree_uuid`, `medias_group_uuid`, `lightbox_uuid`, `website_order_uuid`, `website_quotation_uuid`.

Filters apply only to `all`, not explicit UUID lists. With `all`, omitting a scope can select every accessible media item.

Best-effort and non-atomic: unauthorized changes are skipped; other changes may fail. A zero-match selection can be accepted without jobs or a monitor link.

HTTP 202 means accepted, not completed. Follow the monitor link; immediate reads may not reflect queued changes.

Required roles: **Write** for metadata; **Delete** for permanent media deletion. Other changes have their respective relationship and move requirements.

An active subscription is required.

**Selection:**

| Parameter | Value | Description |
| --- | --- | --- |
| `select` | text | **Required.** Comma-separated media UUIDs, or `all` to select the entire scope. Use at most one scope; without a scope, `all` can select every accessible media item. |
| `medias_count` | integer | Expected selection count for a safety check, not an atomic snapshot. |

**Scope:** See [scope](#get-mediasjson).

**Search filters:** See [search filters](#get-mediasjson).

Example: **add keywords**.

```json
{
  "select": "00000000-0000-0000-0000-000000000001,00000000-0000-0000-0000-000000000002",
  "medias": {
    "add_keywords": "reviewed"
  }
}
```

**Success:** `202` — Work accepted. A zero-match selection can be accepted without jobs or a monitor link.

#### `POST /medias/export.json`

Queue AI analyses or exports through configured profiles.

Set `select` to comma-separated media UUIDs or `all`.
Optionally supply at most one top-level scope: `artist_uuid`, `gallery_uuid`, `gallery_subtree_uuid`, `medias_group_uuid`, `lightbox_uuid`, `website_order_uuid`, `website_quotation_uuid`.

Filters apply only to `all`, not explicit UUID lists. With `all`, omitting a scope can select every accessible media item.

Sends files to the configured AI provider or FTP server; unauthorized or incompatible media are skipped.
AI analyses may update metadata and use microjobs. Availability depends on the subscribed plan.

HTTP 202 means accepted, not completed; do not blindly retry a possibly accepted request.

Required roles: **General administration** on the profiles’ libraries.

An active subscription is required.

**Selection:** See [selection](#patch-mediasjson).

**Scope:** See [scope](#get-mediasjson).

**Search filters:** See [search filters](#get-mediasjson).

**Profiles:**

| Parameter | Value | Description |
| --- | --- | --- |
| `medias[medias_gateway_uuids][]` | UUID list | **Required.** Configured AI assistant or export-profile UUIDs. Each profile processes only media from its own library. Must not be empty. |

Example: **export library**.

```json
{
  "artist_uuid": "00000000-0000-0000-0000-000000000001",
  "select": "all",
  "medias": {
    "medias_gateway_uuids": [
      "00000000-0000-0000-0000-000000000002"
    ]
  }
}
```

**Success:** `202` — Work accepted. A zero-match selection can be accepted without jobs or a monitor link.

### Watermarks

List a library’s watermarks to choose one for an upload. The API returns settings, not source files.
There are no public create, update or delete endpoints for watermarks.

**Response fields:**

| Field | Description |
| --- | --- |
| `uuid` | Watermark UUID. |
| `text` | Watermark text. |
| `file_name` | Watermark image filename. |
| `user_watermark` | Whether the watermark uses an uploaded image rather than generated text. |
| `width` | Watermark width. |
| `height` | Watermark height. |
| `default` | Whether this is the library’s default watermark. |
| `enabled` | Whether the watermark is enabled. |

#### `GET /artists/{artist_uuid}/watermarks.json`

List a library’s watermarks.

Returns a paginated array of watermark settings and UUIDs.

Required roles: **Base** on the library.

An active subscription is required.

**Success:** `200` — Array of basic objects.

### Collections and smart collections

Regular collections group assigned media; smart collections find matching media automatically.
Assign memberships through media updates, not a group membership field.
Membership changes require **Write**, not **Collections administration**.

**Response fields:**

| Field | Description |
| --- | --- |
| `uuid` | Collection or smart-collection UUID. |
| `name` | Collection name. |
| `type` | Collection type; use the matching write prefix. Values: `MediasCollection`, `MediasSmartCollection`. |
| `medias_count` | Media count; can be null for a smart collection and is not an exact matching-result count. |
| `created_at`, `updated_at` | **Details only.** ISO 8601 date/time, or null when unset. |

#### `GET /artists/{artist_uuid}/medias_groups.json`

List regular and smart collections in a library.

Returns paginated basic collection objects.

Required roles: **Base** on the library.

An active subscription is required.

**Success:** `200` — Array of basic objects.

#### `GET /artists/{artist_uuid}/medias_groups/{uuid}.json`

Get collection or smart-collection details.

Matching criteria and saved actions are not returned.

List member or matching media using the `medias_group_uuid` scope on the media-list endpoint.

Required roles: **Base** on the library.

An active subscription is required.

**Success:** `200` — JSON object.

#### `POST /artists/{artist_uuid}/medias_groups.json`

Create a regular collection or smart collection.

Supply exactly one object: `medias_collection` or `medias_smart_collection`, containing a name.

Matching criteria use media search-filter fields and value formats. Smart collections without criteria are empty.

Create a regular collection before configuring saved actions.

Required roles: **Collections administration** on the library.

An active subscription is required.

**Request body:**

| Parameter | Value | Description |
| --- | --- | --- |
| `medias_collection[name]` | text | **Required.** Collection name. |
| `medias_collection[matching_criterias]` | object | Matching criteria for automatic import. Supported fields: `search`, `search_locale`, `with_keywords`, `with_keywords_locale`. |
| `medias_smart_collection[name]` | text | **Required.** Smart-collection name. |
| `medias_smart_collection[matching_criterias]` | object | Smart-collection matching criteria; no criteria means no matching media. Supported fields: `search`, `search_locale`, `with_keywords`, `with_keywords_locale`, `priced`, `pricing_profile`, `media_type`, `orientation`, `min_mpixel`, `max_mpixel`, `uploaded_within`, `model_release`, `property_release`, `published`, `with_title`, `ready_for_sale`, `min_artist_rating`, `max_artist_rating`, `author`, `life_cycle_state`, `created_min`, `created_max`. |

Example: **regular collection**.

```json
{
  "medias_collection": {
    "name": "Client selections"
  }
}
```

Example: **smart collection**.

```json
{
  "medias_smart_collection": {
    "name": "Highly rated images",
    "matching_criterias": {
      "media_type": "Image",
      "min_artist_rating": 4
    }
  }
}
```

**Success:** `201` — Created collection details, with a relative JSON Location.

#### `PUT /artists/{artist_uuid}/medias_groups/{uuid}.json`

Update a regular collection or smart collection.

Supply exactly one object, `medias_collection` or `medias_smart_collection`, matching the existing type.

Matching criteria use media search-filter fields and value formats. Smart collections without criteria are empty.

Saved actions merge with existing settings; omitted actions are preserved.

Effects depend on rights and subscription. Re-import and re-application run asynchronously.

Required roles: **Collections administration** on the library.

An active subscription is required.

**Request body:**

| Parameter | Value | Description |
| --- | --- | --- |
| `medias_collection[name]` | text | Collection name. |
| `medias_collection[matching_criterias]` | object | Matching criteria for automatic import. Supported fields: `search`, `search_locale`, `with_keywords`, `with_keywords_locale`. |
| `medias_collection[saved_actions]` | object | Saved actions merge with existing settings. Effects depend on permissions and the subscribed plan. Supported fields: `title`, `description`, `alt_text`, `add_keywords`, `remove_keywords`, `location`, `city`, `state`, `region`, `country`, `author`, `copyright`, `model_release`, `property_release`, `artist_rating`, `delete_artist_rating`, `life_cycle_state`, `custom_metadata`, `add_to_medias_collection_uuids`, `remove_from_medias_collection_uuids`, `publish_to_gallery_uuids`, `unpublish_from_gallery_uuids`, `add_pricing_profile_uuids`, `remove_pricing_profile_uuids`. |
| `medias_collection[re_import_medias]` | `true` / `false` | Request re-import of matching media. |
| `medias_collection[re_apply_actions_on_medias]` | `true` / `false` | Request re-application of saved actions to media. |
| `medias_smart_collection[name]` | text | Smart-collection name. |
| `medias_smart_collection[matching_criterias]` | object | Smart-collection matching criteria; no criteria means no matching media. Supported fields: `search`, `search_locale`, `with_keywords`, `with_keywords_locale`, `priced`, `pricing_profile`, `media_type`, `orientation`, `min_mpixel`, `max_mpixel`, `uploaded_within`, `model_release`, `property_release`, `published`, `with_title`, `ready_for_sale`, `min_artist_rating`, `max_artist_rating`, `author`, `life_cycle_state`, `created_min`, `created_max`. |

Example: **regular collection**.

```json
{
  "medias_collection": {
    "name": "Reviewed selections",
    "saved_actions": {
      "add_keywords": "reviewed"
    }
  }
}
```

Example: **smart collection**.

```json
{
  "medias_smart_collection": {
    "name": "Highly rated images",
    "matching_criterias": {
      "media_type": "Image",
      "min_artist_rating": 4
    }
  }
}
```

**Success:** `200` — JSON object.

#### `DELETE /artists/{artist_uuid}/medias_groups/{uuid}.json`

Delete a regular collection or smart collection.

Preserves originals unless `delete_medias` requests permanent, asynchronous deletion. Only regular collections support this option.

Verify the selection. Send deletion options at the top level, not under the collection object.

Required roles: **Collections administration**; permanent original-media deletion additionally requires **Delete**.

An active subscription is required.

**Request body:**

| Parameter | Value | Description |
| --- | --- | --- |
| `delete_medias` | `true` / `false` | Request permanent deletion of original media as well as a regular collection; requires library **Delete**. |
| `medias_count` | integer | Optional expected media count when deleting originals. Omit to skip the count check. |
| `medias_in_other_collections` | `true` / `false` | Permit permanent deletion of originals also in other collections or published in galleries. Leave off unless intentional. |

**Success:** `204` — Collection deleted. Any requested original-media deletion can still be running.

### Media gateways

A media gateway is a library profile for sending files to an AI provider for metadata analysis or to an FTP server for export.

Discover profiles and inspect settings/queues with these read-only endpoints. Send media through batch analyses and exports.
Availability depends on the subscribed plan.

Credentials are never returned.

**Response fields:**

| Field | Description |
| --- | --- |
| `uuid` | Gateway profile UUID. |
| `description` | Profile description. |
| `auto_run` | Whether newly uploaded media are automatically analyzed. |
| `queue_state` | Current processing status. |
| `queue_size` | Number of queued items. |
| `gateway_type` | AI metadata assistant or export profile. Values: `ai_metadata`, `export`. |
| `provider_name` | Localized provider display name. |

#### `GET /artists/{artist_uuid}/medias_gateways.json`

List a library’s AI assistants and export profiles.

Returns basic profile information and queue status. File transfers use the export endpoint.

Required roles: **General administration** on the library.

An active subscription is required.

**Success:** `200` — Array of basic objects.

#### `GET /artists/{artist_uuid}/medias_gateways/{uuid}.json`

Get an AI assistant or export profile’s settings and queue status.

Provider-specific settings may be returned for information only and can change without notice. Credentials are not returned.

Required roles: **General administration** on the library.

An active subscription is required.

**Success:** `200` — JSON object.

### Websites

Use the read-only website endpoints to discover sites and retrieve their structure.

Use `urlname`, not the website UUID or hostname, in nested API paths.

**Response fields:**

| Field | Description |
| --- | --- |
| `uuid` | Website UUID. |
| `hostname` | Public hostname. |
| `urlname` | Website URL name (urlname), not its UUID or hostname. |
| `title` | Website title. |
| `roles` | Connected user’s website roles. |
| `roles[r_media]` | Website media. |
| `roles[r_galleries]` | Galleries. |
| `roles[r_sales]` | Users and orders. |
| `roles[r_business]` | Business administration. |
| `roles[r_pages]` | Custom pages. |
| `roles[r_website]` | Website customization. |
| `root_gallery_uuid` | **Details only.** Root gallery UUID. |
| `home_url` | **Details only.** Public home URL. In integration mode it can point to another website. |

#### `GET /websites.json`

List accessible websites and the connected user’s roles.

Returns a paginated array of basic website objects, including the connected user’s website roles.

Required roles: Any website role.

An active subscription is required.

**Success:** `200` — Array of basic objects.

#### `GET /websites/{website}.json`

Get website details, including the connected user’s roles and root gallery UUID.

Use `root_gallery_uuid` for gallery tree traversal or as the parent of a new top-level gallery.

Required roles: Any website role.

An active subscription is required.

**Success:** `200` — JSON object.

### Galleries

Organize a website’s gallery tree and settings. Query media with a gallery scope to find its content.

**Response fields:**

| Field | Description |
| --- | --- |
| `uuid` | Public UUID. |
| `name` | Gallery name. |
| `url_path` | Path on the customer website, not an API endpoint. |
| `full_url_path` | Full path on the customer website. |
| `full_url_path_with_prefix` | Customer-website path including the relevant prefix. |
| `parent_uuid` | Parent gallery UUID; null for a root gallery. |
| `order` | Gallery order. |
| `medias_count` | Direct media count. |
| `full_medias_count` | Media count including descendants. |
| `comments_count` | Comment count. |
| `published_at` | Publication date/time. |
| `hidden` | Whether the gallery is hidden. |
| `created_at`, `updated_at` | ISO 8601 date/time, or null when unset. |
| `subgalleries_count` | **List only.** Tree endpoint only: direct subgallery count. |
| `description` | **Details only.** Gallery introduction. |
| `meta_title` | **Details only.** Search-engine title. |
| `meta_description` | **Details only.** Search-engine description. |
| `public_access` | **Details only.** Public-access setting. |
| `has_password` | **Details only.** Whether a password is configured; the password is not returned. |
| `has_acl` | **Details only.** Whether visitor authorizations are configured. |
| `cover_media_uuid` | **Details only.** Cover media UUID, or null. |
| `gallery_display_style_uuid` | **Details only.** Gallery display-style UUID or built-in style name. |

#### `GET /websites/{website}/galleries.json`

List basic galleries for one website.

Returns a paginated array of basic gallery objects.

Required roles: **Website media** or **Galleries**.

An active subscription is required.

**Success:** `200` — Array of basic objects.

#### `GET /websites/{website}/galleries/{uuid}.json`

Get gallery details.

List published media using the `gallery_uuid` scope on the media-list endpoint, or `gallery_subtree_uuid` to include descendants.

Required roles: **Galleries**.

An active subscription is required.

**Success:** `200` — JSON object.

#### `POST /websites/{website}/galleries.json`

Create a gallery under a parent on the same website.

Supply `name` and `parent_uuid` under `gallery`. Unauthorized settings may be ignored even on success; verify the returned resource.

Copy from the same website; later source changes do not propagate. Price copies are additive: avoid unintended repeats.

Required roles: **Galleries**.

An active subscription is required.

**Gallery fields:**

| Parameter | Value | Description |
| --- | --- | --- |
| `gallery[name]` | text | Gallery name. |
| `gallery[description]` | text | Gallery introduction. |
| `gallery[parent_uuid]` | UUID | Parent gallery UUID in the same website. Updating it moves the gallery within the tree. |
| `gallery[url_path]` | text | URL path; blank allows automatic assignment from the name. |
| `gallery[meta_title]` | text | Search-engine title. |
| `gallery[meta_description]` | text | Search-engine description. |
| `gallery[gallery_display_style_uuid]` | text | Display-style UUID or built-in style name. |
| `gallery[cover_media_uuid]` | text | Accessible website media UUID, or `auto` to choose a cover automatically. |
| `gallery[hidden]` | `true` / `false` | Hide the gallery. Availability depends on the subscribed plan. |
| `gallery[password]` | text | Set a password; empty string clears it. Availability depends on the subscribed plan. |
| `gallery[indexing]` | integer | Search indexing: 0 never, 1 automatic, 2 always. Range: 0–2. |
| `gallery[content_order]` | text | Gallery content ordering. Values: `manual-first`, `manual-last`, `file_name`, `file_name_nat`, `published_at`, `original_created_at`, `created_at`, `updated_at`, `artist_rating`, `life_cycle`. |
| `gallery[content_order_direction]` | text | Content order direction. Values: `asc`, `desc`. |
| `gallery[order_subgalleries]` | `true` / `false` | Enable automatic subgallery ordering. |
| `gallery[media_uuids_order]` | text | Comma-separated media UUIDs specifying manual content order. |
| `gallery[copy_settings_from_uuid]` | UUID | Copy display, ordering, comments, rating, visibility and default download/access settings. Excludes the introduction, individual visitor authorizations and pricing; use their separate copy fields. |
| `gallery[copy_authorizations_from_uuid]` | UUID | Copy visitor authorizations, merging individual authorizations with existing ones. |
| `gallery[copy_cust_downloads_from_uuid]` | UUID | Copy custom download settings. Availability depends on the subscribed plan. |
| `gallery[copy_description_from_uuid]` | UUID | Copy the introduction and its translations. |
| `gallery[copy_prices_from_uuid]` | UUID | Add prices and purchase-link text with translations, without removing existing prices. Availability depends on the subscribed plan. |
| `gallery[copy_all_prices_from_uuid]` | UUID | Copy prices and batch-purchase settings. Create/update pricing-profile assignment automations from the source, preserving other actions. Availability depends on the subscribed plan. |

Example: **new gallery**.

```json
{
  "gallery": {
    "name": "Client selection",
    "parent_uuid": "00000000-0000-0000-0000-000000000001"
  }
}
```

**Success:** `201` — Created gallery details with a relative JSON Location.

#### `PUT /websites/{website}/galleries/{uuid}.json`

Update gallery settings.

Supply changes under `gallery`; omitted fields are preserved. Unauthorized settings may be ignored even on success; verify the returned resource.

Copy from the same website; later source changes do not propagate. Price copies are additive: avoid unintended repeats.

Required roles: **Galleries**.

An active subscription is required.

**Gallery fields:** See [gallery fields](#post-websiteswebsitegalleriesjson).

Example: **update settings**.

```json
{
  "gallery": {
    "name": "Client selections",
    "description": "Select your favorite images.",
    "content_order": "file_name",
    "content_order_direction": "asc"
  }
}
```

**Success:** `200` — JSON object.

#### `DELETE /websites/{website}/galleries/{uuid}.json`

Delete a gallery and its descendants.

Preserves originals by default. `delete_medias` can permanently delete original media; deletion is queued separately and may outlast this request.

Verify the selection. Send deletion options at the top level, not under `gallery`.

Required roles: **Galleries**; **Delete** on each library to delete its originals. Other originals are preserved.

An active subscription is required.

**Request body:**

| Parameter | Value | Description |
| --- | --- | --- |
| `delete_medias` | `true` / `false` | Also request permanent deletion of original media in the subtree; requires **Delete** on each library. |
| `medias_count` | integer | Optional expected subtree media count when deleting originals. Omit to skip the count check. |
| `medias_in_other_collections` | `true` / `false` | Permit deletion of originals also published elsewhere or in collections. Leave off unless intentional. |
| `recurse` | `true` / `false` | Defaults to true. False rejects deletion when the gallery has direct children. |

**Success:** `204` — Gallery deleted; separately queued original-media deletion may still be running.

#### `GET /websites/{website}/galleries/{uuid}/subgalleries.json`

List a gallery’s ancestors, itself and direct children.

Returns ancestors, self, then direct children—not all descendants. Paginated together; later pages do not repeat ancestors.
Assemble the tree with `parent_uuid`.

Tree access does not grant gallery-detail or media access.

Required roles: Any website role.

An active subscription is required.

**Query parameters:**

| Parameter | Value | Description |
| --- | --- | --- |
| `filter` | object | Optional direct-child filter. |
| `filter[name]` | text | Exact name match for direct children; ancestors and self remain. |

**Success:** `200` — Array of basic objects.

### Gallery display styles

Use a display-style UUID as `gallery_display_style_uuid` under `gallery` to assign a style to a gallery.
Full style settings are not returned. There are no public create, update or delete endpoints for display styles.

**Response fields:**

| Field | Description |
| --- | --- |
| `uuid` | Display-style UUID. |
| `name` | Display-style name. |
| `builtin` | Built-in style name, or null for a custom style. |

#### `GET /websites/{website}/gallery_display_styles.json`

List a website’s gallery display styles.

Returns a paginated array of basic display-style objects and their UUIDs.

Required roles: **Website customization** on the website.

An active subscription is required.

**Success:** `200` — Array of basic objects.

### Background jobs

List outstanding work, including scheduled jobs and retries. This is not a history; jobs cannot be modified or canceled.

- Account holders see account jobs.
- Team members see only jobs attributed to themselves in that account.

**Job status:**

- `queued`: eligible to run.
- `running`: being processed.
- `scheduled`: first execution is scheduled for later.
- `retrying`: waiting to retry after a failure.
- `completed`: no longer outstanding, if returned.

**Completion, retries and polling:**

Completed jobs and jobs that stop retrying disappear from the list.
**A disappearing job or empty monitor does not prove success:** verify the affected resources.

Failed jobs can return to `queued` or `running` when retried. Work is best-effort, with no guaranteed completion time.
For jobs retrying over several days, contact support with the request ID.

- Poll infrequently with backoff.
- A monitor may not show every workflow stage at once.
- Request IDs do not grant access.

**Response fields:**

| Field | Description |
| --- | --- |
| `action` | Action name, such as medias.update or media.process. |
| `created_at` | Time the job was queued. |
| `run_at` | Earliest scheduled execution or retry time, not a promised start/completion time. |
| `status` | Current job status. Values: `queued`, `running`, `scheduled`, `retrying`, `completed`. |
| `user_name` | User’s display name, or a localized label when a name is unavailable. |
| `request_id` | Originating request ID when visible to the caller, otherwise null. |

#### `GET /jobs.json`

List customer-visible outstanding background jobs.

Filter by originating request ID and/or action. Account holders see account jobs; team members see only their own.

**Job filters:**

| Parameter | Value | Description |
| --- | --- | --- |
| `filter[request_id]` | text | Request identifier returned in X-Request-ID and used by monitor links. |
| `filter[action]` | text | Exact action name or a prefix ending in `.*`, such as `media.*`. |

**Success:** `200` — Array of basic objects.

#### `GET /jobs/requests/{request_id}.json`

List outstanding background jobs for one originating request.

Account holders see account jobs; team members see only their own. Optionally filter by action.

**Path parameters:**

| Parameter | Value | Description |
| --- | --- | --- |
| `request_id` | text | **Required.** Request identifier returned in X-Request-ID and used by monitor links. |

**Query parameters:**

| Parameter | Value | Description |
| --- | --- | --- |
| `filter` | object | Optional background-job filters. |
| `filter[action]` | text | Exact action name or a prefix ending in `.*`, such as `media.*`. |

**Success:** `200` — Array of basic objects.

### Documentation search

Find relevant articles in the PhotoDeck Help Center.

**Response fields:**

| Field | Description |
| --- | --- |
| `title` | Matching article’s title. |
| `url` | HTML article URL, not JSON or Markdown. Fetch it to read the matching content. |
| `excerpt` | Matching excerpt; may contain HTML markup. |

#### `GET /{locale}/help.json`

Search the PhotoDeck Help Center.

Results contain a title, an HTML page URL and an excerpt; fetch the HTML page to read the article.

Use the connected user’s preferred locale when supported, otherwise `en`.

**Path parameters:**

| Parameter | Value | Description |
| --- | --- | --- |
| `locale` | text | **Required.** Help locale. Use preferred_locale when supported, otherwise `en`. Values: `en`, `fr`, `de`. |

**Query parameters:**

| Parameter | Value | Description |
| --- | --- | --- |
| `search` | text | **Required.** Question or search text. |

**Success:** `200` — Array of basic objects.

<!-- END GENERATED API_REFERENCE -->

## Uploading and replacing files

Uploads have three steps: request upload instructions, transfer bytes to storage, and notify PhotoDeck that the transfer completed.

The storage request is **not** an authenticated API request: use only the returned upload instructions,
not your OAuth bearer token or client credentials.

### 1. Request an upload

Uploading requires **Write** on the destination library. Replacing an existing processed file also requires **Delete** on that library.

**Take care when replacing a file:** requesting a replacement can queue deletion of previous file versions before the new transfer completes.
The previous file may not remain available if the upload fails.

For filename replacement, check **Delete** first. Without it, `replace` can be ignored and the request may create a new media item instead.

Send `POST /medias.json` with these parameters in a JSON body:

<!-- BEGIN GENERATED UPLOAD_REQUEST -->

| Parameter | Value | Description |
| --- | --- | --- |
| `media[content][upload_location]` | text | **Required.** Request upload instructions. Values: `REQUEST`. |
| `media[content][file_name]` | text | **Required.** Original filename when requesting instructions; returned filename when confirming. |
| `media[content][file_size]` | integer | **Required.** Original file length in bytes, measured from the actual file. |
| `media[content][mime_type]` | text | **Required.** Original MIME type when requesting instructions; returned MIME type when confirming. |
| `media[content][capabilities]` | text | Send `raw` only if the client supports sending a raw file body. |

<!-- END GENERATED UPLOAD_REQUEST -->

You can list available watermarks with `GET /artists/{artist_uuid}/watermarks.json`.
When requesting upload instructions, choose the watermark behavior as follows:

- For new media, omit `media[watermark_uuid]` to use the library's default watermark.

- When replacing existing media, omit it to keep that media item's current watermark choice.

- To disable watermarking, send `media[watermark_uuid]=none`. This also applies when replacing a known media UUID with `PUT`.

Metadata fields and media `include` values can also be supplied.
The response is a detailed media object with upload fields at its top level:

<!-- BEGIN GENERATED UPLOAD_RESPONSE -->

| Parameter | Value | Description |
| --- | --- | --- |
| `uuid` | UUID | **Required.** Public UUID. |
| `file_name` | text | **Required.** Returned filename; can differ from the submitted filename. Use it for upload transfer/confirmation. |
| `mime_type` | text | **Required.** Returned MIME type; can differ from the submitted MIME type. Use it for upload transfer/confirmation. |
| `upload_location` | text | **Required.** Returned storage location; send back unchanged on confirmation. |
| `upload_url` | uri | **Required.** Temporary storage URL. Use unchanged; never attach PhotoDeck OAuth credentials. |
| `upload_method` | text | **Required.** HTTP method for transferring the file; do not hardcode a method or storage host. |
| `upload_file_param` | text | **Required.** Multipart file field name, or null to send a raw file body. |
| `upload_params` | object | **Required.** Storage parameters; send all before file content in multipart uploads. Can be null for raw-body uploads. |
| `upload_expires_in` | integer | **Required.** Lifetime of the upload authorization in seconds. Request fresh instructions after expiry. |

<!-- END GENERATED UPLOAD_RESPONSE -->

Creation returns `201`; replacing an existing media item returns `200` and retains its UUID.

Keep the original file length in bytes locally: you will need it when [confirming completion](#3-confirm-completion).

Before transferring the file, check that the request succeeded **and** that the response contains upload instructions, including `upload_url`.
A successful HTTP status alone does not guarantee that the upload fields were accepted.

### 2. Transfer the file

- If `upload_file_param` is not `null`, use `upload_method` with `multipart/form-data`.
  Include every `upload_params` entry before the file field. Let the HTTP library generate the multipart boundary.

- If `upload_file_param` is `null`, use `upload_method` with the raw file bytes as the body.
  Set `Content-Type` to the returned `mime_type` and `Content-Length` to the file's byte length.

- Use `upload_url` exactly as returned: do not modify its query string or attach API bearer credentials.

- Check the storage response for success before proceeding. Its response may be empty or use a storage-specific format rather than JSON.

### 3. Confirm completion

Send `PUT /medias/{uuid}.json` with these parameters in a JSON body:

<!-- BEGIN GENERATED UPLOAD_CONFIRMATION -->

| Parameter | Value | Description |
| --- | --- | --- |
| `media[content][upload_location]` | text | **Required.** Returned upload location when confirming, or `REQUEST` when requesting instructions. |
| `media[content][file_name]` | text | **Required.** Original filename when requesting instructions; returned filename when confirming. |
| `media[content][file_size]` | integer | **Required.** Original file length in bytes, measured from the actual file. |
| `media[content][mime_type]` | text | **Required.** Original MIME type when requesting instructions; returned MIME type when confirming. |

<!-- END GENERATED UPLOAD_CONFIRMATION -->

The response contains the updated media object, but file processing may still be running.
If you need to wait for `processed=true`, check `GET /medias/{uuid}.json` periodically, increasing the delay between checks.

If a failure contains `stop_with_error=true`, stop the upload queue and report the error instead of continuing other files.

### Complete curl workflow

This Bash example uses the authenticated [`api_curl` helper](#bearer-curl-example), `jq`, and GNU `stat`/`file`.
Set `file` and `artist_uuid` to your local filename and destination library UUID.

```bash
file='/path/to/image.jpg'
artist_uuid='LIBRARY_UUID'
file_size=$(stat -c '%s' "$file")
mime_type=$(file --brief --mime-type "$file")

upload_request=$(jq -n \
  --arg artist_uuid "$artist_uuid" --arg file_name "$(basename "$file")" \
  --argjson file_size "$file_size" --arg mime_type "$mime_type" \
  '{artist_uuid: $artist_uuid, media: {content: {
    upload_location: "REQUEST", file_name: $file_name, file_size: $file_size, mime_type: $mime_type
  }}}') || exit 1
upload=$(api_curl POST '/medias.json' --json "$upload_request") || exit 1

uuid=$(jq -er '.uuid' <<< "$upload") || exit 1
upload_url=$(jq -er '.upload_url' <<< "$upload") || exit 1
upload_method=$(jq -er '.upload_method' <<< "$upload") || exit 1
upload_file_param=$(jq -r '.upload_file_param // empty' <<< "$upload")
upload_location=$(jq -er '.upload_location' <<< "$upload") || exit 1
file_name=$(jq -er '.file_name' <<< "$upload") || exit 1
mime_type=$(jq -er '.mime_type' <<< "$upload") || exit 1

if [[ -n "$upload_file_param" ]]; then
  form_args=()
  while IFS= read -r param; do
    form_args+=(--form-string "$param")
  done < <(jq -r '.upload_params | to_entries[] | "\(.key)=\(.value)"' <<< "$upload")
  curl --silent --show-error --fail-with-body --request "$upload_method" \
    "${form_args[@]}" --form "$upload_file_param=@\"$file\";type=$mime_type" "$upload_url" || exit 1
else
  curl --silent --show-error --fail-with-body --request "$upload_method" \
    --header "Content-Type: $mime_type" --header "Content-Length: $file_size" \
    --data-binary "@$file" "$upload_url" || exit 1
fi

confirmation=$(jq -n \
  --arg upload_location "$upload_location" --arg file_name "$file_name" \
  --argjson file_size "$file_size" --arg mime_type "$mime_type" \
  '{media: {content: {
    upload_location: $upload_location, file_name: $file_name, file_size: $file_size, mime_type: $mime_type
  }}}') || exit 1
api_curl PUT "/medias/$uuid.json" --json "$confirmation"
```

To replace a known media UUID, request new instructions with `PUT /medias/{uuid}.json` using the same `REQUEST` fields as
[Request an upload](#1-request-an-upload), then [transfer the file](#2-transfer-the-file) and [confirm completion](#3-confirm-completion).
This is different from `POST` with `media[replace]=true`, which searches for a filename match.

## Client implementation checklist

- Use browser authorization with S256 PKCE. Never collect the user's PhotoDeck password or OTP in your application.

- Use query strings for GET requests and nested JSON bodies for mutations, with native booleans, numbers and arrays.

- Store access and refresh tokens securely, allow only one refresh at a time per connection, and never forward API credentials to storage upload hosts.

- Discover accessible identifiers through lists; do not invent numeric IDs or assume a website UUID is its path identifier.

- Use basic lists for discovery, retrieve details for metadata, and use documented `include` values for relationship UUIDs.

- Follow next links unchanged. Treat count/page headers as optional and deduplicate changing lists by UUID.

- Check status and content type before parsing; do not parse empty `204` bodies. Treat `202` batch responses as pending work.

- Use `rel="monitor"` links to check jobs infrequently, increasing the delay between lookups.
  Treat `completed` jobs as no longer outstanding; disappearance alone does not prove success.

- Add or remove relationships incrementally unless replacing all memberships or publications is intentional.
  Move media between libraries separately from assigning collections.

- Honor rate limits, preserve the method and body on trusted `307` retries, and do not blindly repeat mutations after uncertain failures.

- Complete all three upload steps and wait for processing when necessary. Check upload authorization expiry and stop the queue when instructed.

- Test with account-holder and restricted team-member accounts, empty lists, multiple pages, inaccessible resources and failed uploads.

## Migrating from the legacy XML API

The XML API is deprecated. Use the [JSON API](#getting-started) for new integrations, and migrate existing XML clients to JSON.

Migration involves more than changing the response parser: authentication, parameter names and response handling also change.

### URLs and authentication

Change `.xml` endpoint suffixes to `.json`. Replace the custom signed application headers, HTTP Basic user login and session-cookie
authentication with OAuth authorization code, S256 PKCE and bearer tokens. Existing XML API keys/secrets are not OAuth credentials.

Register redirect URIs on your application and obtain its OAuth client ID. Users enter passwords and OTP codes only on PhotoDeck's browser sign-in page.

The deprecated XML API retains its existing signing, Basic authentication and session behavior; these are not supported by the JSON API.

Send mutation bodies as JSON objects with `Content-Type: application/json`. Convert bracketed parameter names to nested objects and arrays as shown under [Parameters and identifiers](#parameters-and-identifiers).

For batch media changes, use `PATCH /medias.json` instead of `/medias/batch_update.xml`.

### Response parsing and status handling

| Legacy behavior                                                    | JSON client change                                                                                          |
| ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------- |
| XML `<reply>` envelope, resource wrappers and separate UUID fields | Parse a bare object/array; use its `uuid`, not `media_uuid` or `gallery_uuid`.                              |
| `view` parameter selecting response fields                         | Remove it. Use basic lists, retrieve resource details and request documented `include` values.              |
| Embedded `total_pages` in the response body                        | Follow `Link` with `rel="next"`; other pagination headers are optional.                                     |
| Separate collection/smart-collection arrays                        | Read the combined `/artists/{artist_uuid}/medias_groups.json` array and inspect `type`.                     |
| XML `message`, `error` and resource-validation elements            | Read JSON `notice`, `error` and optional `errors`. Use HTTP status rather than message matching.            |
| Success bodies on logout/deletion                                  | Handle an empty `204` body without JSON parsing.                                                            |
| Unauthenticated `whoami` returns `202` and a placeholder user      | Handle `401` with the standard error body; authenticate before reading the profile.                         |
| Batch updates treated as immediate success                         | Handle `202` notices, follow `rel="monitor"` links with backoff and verify affected resource results.       |
| Broad XML details or automation payloads                           | Adjust to the explicit JSON field lists in this guide; do not assume every XML field has been carried over. |

Not every expanded view has a JSON equivalent. Use separate media queries for paginated contents.

Do not expect the old envelope's request/query metadata, type-named wrappers or pagination fields on resources/tree nodes.

JSON pagination defaults can differ from the legacy endpoints. Do not depend on an old default page size or the special unpaginated behavior
some XML list endpoints allowed when `page` and `per_page` were absent.

For gallery tree pagination, follow the sequence described under [Gallery tree traversal](#get-websiteswebsitegalleriesuuidsubgalleriesjson).

### Rename relationship parameters

Use canonical UUID names even when an old parameter happened to carry a UUID despite being called `_id`.
The old aliases are supported only for XML API requests. Use the documented JSON names in new clients.

| Old parameter                                                                                      | JSON parameter                                                                                        |
| -------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| Top-level `artist_id`, `gallery_id`, `medias_group_id`, `lightbox_id`                              | `artist_uuid`, `gallery_uuid`, `medias_group_uuid`, `lightbox_uuid`                                   |
| Top-level `gallery_subtree_id`, `website_order_id`, `website_quotation_id`                         | `gallery_subtree_uuid`, `website_order_uuid`, `website_quotation_uuid`                                |
| `media[artist_id]` / `medias[artist_id]`                                                           | `media[artist_uuid]` / `medias[artist_uuid]`                                                          |
| `media[galleries][]`                                                                               | `media[gallery_uuids][]`                                                                              |
| `media[medias_collections][]`                                                                      | `media[medias_collection_uuids][]`                                                                    |
| `media[add_to_medias_collections][]`                                                               | `media[add_to_medias_collection_uuids][]`                                                             |
| `media[remove_from_medias_collections][]`                                                          | `media[remove_from_medias_collection_uuids][]`                                                        |
| `media[publish_to_galleries][]`, `media[unpublish_from_galleries][]`                               | `media[publish_to_gallery_uuids][]`, `media[unpublish_from_gallery_uuids][]`                          |
| `media[add_pricing_profiles][]`, `media[remove_pricing_profiles][]`                                | `media[add_pricing_profile_uuids][]`, `media[remove_pricing_profile_uuids][]`                         |
| `media[pricing_profiles]`                                                                          | `media[pricing_profile_uuids]` (replacement is account-holder restricted; prefer incremental changes) |
| `media[add_to_lightbox]`, `media[remove_from_lightbox]`                                            | `media[add_to_lightbox_uuid]`, `media[remove_from_lightbox_uuid]`                                     |
| `media[add_to_quotation]`, `media[remove_from_quotation]`                                          | `media[add_to_quotation_uuid]`, `media[remove_from_quotation_uuid]`                                   |
| `gallery[parent]`                                                                                  | `gallery[parent_uuid]`                                                                                |
| `gallery[display_style]`, `gallery[cover_media]`                                                   | `gallery[gallery_display_style_uuid]`, `gallery[cover_media_uuid]`                                    |
| `gallery[medias_order]`                                                                            | `gallery[media_uuids_order]`                                                                          |
| `gallery[copy_settings_from]`, `gallery[copy_authorizations_from]`                                 | `gallery[copy_settings_from_uuid]`, `gallery[copy_authorizations_from_uuid]`                          |
| `gallery[copy_cust_downloads_from]`, `gallery[copy_description_from]`, `gallery[copy_prices_from]` | Corresponding `gallery[..._uuid]` fields.                                                             |

The media relationship renames also apply under plural `medias[...]` for batch updates and in saved actions where those actions are supported.

Replace old batch selection forms such as `medias[on]`, `medias[medias]` and `medias[medias_group]` with top-level `select` and the documented scope UUID.

Use the parameters explicitly documented for each endpoint rather than deriving names from the old API.

### Upload migration

Keep the three-step workflow, but use `.json` and bearer authentication for the initial and completion requests.

Read upload instructions directly from the top-level response object, not a separate `media_uuid` or XML media wrapper.
The fields and transfer rules are described in [Uploading and replacing files](#uploading-and-replacing-files).

The storage transfer itself does not change to JSON and must not receive API credentials.

