Hub Media Import API
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
POST /wp-json/purple/v3/import/mediaAuthentication Required: Yes (Bearer token). Please take a look into the Hub Import API for more information Required Capability: edit_posts
Table of Contents
- Quick StartQuick Start
- Request ParametersRequest Parameters
- Response FormatResponse Format
- Use Cases & ExamplesUse Cases & Examples
- Deduplication ModesDeduplication Modes
- Error HandlingError Handling
- Best PracticesBest Practices
Quick Start
Upload a New Image
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
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)
{
"attachmentId": 123,
"mediaHash": "a7f3c9d2e8b1f4a6c5d8e9f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1",
"url": "https://your-site.com/wp-content/uploads/2024/01/image.jpg"
}Error Response
{
"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
curl -X POST 'https://your-site.com/wp-json/purple/v3/import/media' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-F '[email protected]' \
-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
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:
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:
curl -X POST 'https://your-site.com/wp-json/purple/v3/import/media' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-F 'attachmentId=123' \
-F '[email protected]' \
-F 'metadata[title]=Replaced Image'4. Update by Media Hash
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
curl -X POST 'https://your-site.com/wp-json/purple/v3/import/media' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-F '[email protected]' \
-F 'targetPosts[]=101' \
-F 'targetPosts[]=202' \
-F 'targetPosts[]=303' \
-F 'metadata[title]=Shared Image'6. Multilingual Upload (Polylang)
Upload English version:
curl -X POST 'https://your-site.com/wp-json/purple/v3/import/media' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-F '[email protected]' \
-F 'metadata[languageSlug]=en' \
-F 'metadata[duplicateAssetsByLanguage]=true' \
-F 'metadata[title]=English Product' \
-F 'metadata[alt]=English description'Upload German version (reuses file!):
curl -X POST 'https://your-site.com/wp-json/purple/v3/import/media' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-F '[email protected]' \
-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)
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:
# First upload
curl -F '[email protected]' ...
# Response: attachmentId: 123
# Second upload (same file)
curl -F '[email protected]' ...
# 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:
# Upload English version
curl -F 'metadata[languageSlug]=en' -F 'metadata[duplicateAssetsByLanguage]=true' -F '[email protected]' ...
# Response: attachmentId: 123, file: /uploads/image.jpg
# Upload German version (same file content)
curl -F 'metadata[languageSlug]=de' -F 'metadata[duplicateAssetsByLanguage]=true' -F '[email protected]' ...
# 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 |