---
title: Hub Media Import API
slug: editorial/hub-media-import-api
docTags: 
createdAt: 2026-04-16T12:03:42.966Z
---

## Overview

The Media Import API provides a powerful endpoint for uploading, updating, and managing WordPress media attachments programmatically. It supports both direct file uploads and URL-based imports, with advanced features like hash-based deduplication and multilingual support.

## Endpoint

```javascript
POST /wp-json/purple/v3/import/media
```

**Authentication Required:** Yes (Bearer token). Please take a look into the [Hub Import API](https://docs.purplepublish.com/editorial/hub-import-api#authentication) for more information
**Required Capability:** `edit_posts`

***

## Table of Contents

1. [Quick Start](./#quick-start)
2. [Request Parameters](./#request-parameters)
3. [Response Format](./#response-format)
4. [Use Cases & Examples](./#use-cases--examples)
5. [Deduplication Modes](./#deduplication-modes)
6. [Error Handling](./#error-handling)
7. [Best Practices](docId:)

***

## Quick Start

### Upload a New Image

```bash
curl -X POST 'https://your-site.com/wp-json/purple/v3/import/media' \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -F 'file=@/path/to/image.jpg' \
  -F 'metadata[title]=My Image' \
  -F 'metadata[alt]=Image description'
```

### Import from URL

```bash
curl -X POST 'https://your-site.com/wp-json/purple/v3/import/media' \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "fileUrl": "https://example.com/image.jpg",
    "metadata": {
      "title": "Remote Image",
      "alt": "Description"
    }
  }'
```

***

## Request Parameters

### File Source (Choose One)

| Parameter | Type   | Description                              |
| --------- | ------ | ---------------------------------------- |
| `file`    | File   | Direct file upload (multipart/form-data) |
| `fileUrl` | String | URL to download the file from            |

**Note:** Provide either `file` OR `fileUrl`, not both. When an existing attachmentId or mediaHash (see below) is provided, it is not necessary to provide one of these.

### Update Existing Attachment (Optional)

| Parameter      | Type    | Description                         |
| -------------- | ------- | ----------------------------------- |
| `attachmentId` | Integer | WordPress attachment ID to update   |
| `mediaHash`    | String  | SHA-256 hash to identify attachment |

**Note:** If provided without a new file, only metadata is updated.

### Metadata (Optional)

| Field                     | Type    | Description                 |
| ------------------------- | ------- | --------------------------- |
| `metadata[title]`         | String  | Attachment title            |
| `metadata[alt]`           | String  | Alt text for images         |
| `metadata[caption]`       | String  | Caption text                |
| `metadata[description]`   | String  | Full description            |
| `metadata[focalPoint][x]` | Integer | Focal point X               |
| `metadata[focalPoint][y]` | Integer | Focal point Y               |
| `metadata[acf]`           | Object  | Advanced Custom Fields data |

### Multilingual Support (Polylang)

| Parameter                             | Type    | Description                            |
| ------------------------------------- | ------- | -------------------------------------- |
| `metadata[languageSlug]`              | String  | Language code (e.g., 'en', 'de', 'fr') |
| `metadata[duplicateAssetsByLanguage]` | Boolean | Enable language-based deduplication    |

### Post Association (Optional)

| Parameter     | Type  | Description                           |
| ------------- | ----- | ------------------------------------- |
| `targetPosts` | Array | Post IDs to associate with this media |

***

## Response Format

### Success Response (200 OK)

```json
{
  "attachmentId": 123,
  "mediaHash": "a7f3c9d2e8b1f4a6c5d8e9f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1",
  "url": "https://your-site.com/wp-content/uploads/2024/01/image.jpg"
}
```

### Error Response

```json
{
  "code": "invalid_file_type",
  "message": "Invalid file type. File type not allowed by WordPress.",
  "data": {
    "status": 400
  }
}
```

***

## Use Cases & Examples

### 1. Upload New Image with Metadata

```bash
curl -X POST 'https://your-site.com/wp-json/purple/v3/import/media' \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -F 'file=@product.jpg' \
  -F 'metadata[title]=Product Photo' \
  -F 'metadata[alt]=Red bicycle in store' \
  -F 'metadata[caption]=Our new 2024 model' \
  -F 'metadata[description]=High-quality mountain bike' \
  -F 'metadata[focalPoint][x]=60' \
  -F 'metadata[focalPoint][y]=40'
```

### 2. Import from External URL

```bash
curl -X POST 'https://your-site.com/wp-json/purple/v3/import/media' \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "fileUrl": "https://cdn.example.com/images/product-123.jpg",
    "metadata": {
      "title": "Product 123",
      "alt": "Product photo",
      "caption": "Imported from CDN"
    }
  }'
```

### 3. Update Existing Attachment

**Update metadata only:**

```bash
curl -X POST 'https://your-site.com/wp-json/purple/v3/import/media' \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "attachmentId": 123,
    "metadata": {
      "title": "Updated Title",
      "alt": "Updated alt text"
    }
  }'
```

**Replace file and update metadata:**

```bash
curl -X POST 'https://your-site.com/wp-json/purple/v3/import/media' \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -F 'attachmentId=123' \
  -F 'file=@new-image.jpg' \
  -F 'metadata[title]=Replaced Image'
```

### 4. Update by Media Hash

```bash
curl -X POST 'https://your-site.com/wp-json/purple/v3/import/media' \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "mediaHash": "a7f3c9d2e8b1f4a6c5d8e9f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1",
    "metadata": {
      "caption": "Updated via hash"
    }
  }'
```

### 5. Associate with Multiple Posts

```bash
curl -X POST 'https://your-site.com/wp-json/purple/v3/import/media' \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -F 'file=@shared-image.jpg' \
  -F 'targetPosts[]=101' \
  -F 'targetPosts[]=202' \
  -F 'targetPosts[]=303' \
  -F 'metadata[title]=Shared Image'
```

### 6. Multilingual Upload (Polylang)

**Upload English version:**

```bash
curl -X POST 'https://your-site.com/wp-json/purple/v3/import/media' \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -F 'file=@product.jpg' \
  -F 'metadata[languageSlug]=en' \
  -F 'metadata[duplicateAssetsByLanguage]=true' \
  -F 'metadata[title]=English Product' \
  -F 'metadata[alt]=English description'
```

**Upload German version (reuses file!):**

```bash
curl -X POST 'https://your-site.com/wp-json/purple/v3/import/media' \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -F 'file=@product.jpg' \
  -F 'metadata[languageSlug]=de' \
  -F 'metadata[duplicateAssetsByLanguage]=true' \
  -F 'metadata[title]=Deutsches Produkt' \
  -F 'metadata[alt]=Deutsche Beschreibung'
```

**Result:** Two attachment posts, one physical file.

### 7. Advanced Custom Fields (ACF)

```bash
curl -X POST 'https://your-site.com/wp-json/purple/v3/import/media' \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "fileUrl": "https://example.com/image.jpg",
    "metadata": {
      "title": "Product Image",
      "acf": {
        "photographer": "John Doe",
        "license": "CC-BY-SA",
        "shooting_date": "2024-01-15"
      }
    }
  }'
```

***

## Deduplication Modes

The API supports two deduplication strategies:

### 1. Hash-Based Deduplication (Default)

**How it works:**

- Calculates SHA-256 hash of file content
- If hash exists → Returns existing attachment
- Prevents duplicate files globally

**Best for:**

- Single-language sites
- Preventing duplicate uploads
- Saving disk space

**Example:**

```bash
# First upload
curl -F 'file=@image.jpg' ...
# Response: attachmentId: 123

# Second upload (same file)
curl -F 'file=@image.jpg' ...
# Response: attachmentId: 123 (same!)
```

### 2. Language-Based Deduplication

**How it works:**

- Checks by filename + language
- If filename exists in same language → Returns existing
- If filename exists in different language → Creates new attachment, **reuses file**
- Creates separate attachment posts per language
- All language variants share the same physical file

**Best for:**

- Multilingual sites (Polylang)
- Language-specific metadata (alt text, captions)
- Disk space efficiency

**Example:**

```bash
# Upload English version
curl -F 'metadata[languageSlug]=en' -F 'metadata[duplicateAssetsByLanguage]=true' -F 'file=@image.jpg' ...
# Response: attachmentId: 123, file: /uploads/image.jpg

# Upload German version (same file content)
curl -F 'metadata[languageSlug]=de' -F 'metadata[duplicateAssetsByLanguage]=true' -F 'file=@image.jpg' ...
# Response: attachmentId: 124, file: /uploads/image.jpg (SAME file!)
```

**Result:**

- ✅ Two attachment posts (#123 EN, #124 DE)
- ✅ Language-specific metadata
- ✅ One physical file on disk
- ✅ No disk space waste

**When to use:**

- Content with language-specific alt text/captions
- PDFs with embedded text in different languages
- Images with localized graphics/text

***

## Error Handling

### Common Error Codes

| Code                     | HTTP Status | Description                                           |
| ------------------------ | ----------- | ----------------------------------------------------- |
| `invalid_file_upload`    | 400         | Missing or invalid file                               |
| `invalid_file_type`      | 400         | File type not allowed by WordPress                    |
| `file_too_large`         | 400         | File exceeds 50MB limit                               |
| `invalid_url`            | 400         | Invalid or malformed URL                              |
| `ssrf_protection`        | 400         | URL points to private/reserved IP                     |
| `attachment_not_found`   | 404         | Attachment ID doesn't exist                           |
| `media_hash_not_found`   | 404         | No attachment with that hash                          |
| `polylang_required`      | 400         | duplicateAssetsByLanguage set but Polylang not active |
| `upload_directory_error` | 500         | WordPress upload directory issue                      |

