XF 2.2 Using REST API to post a media item, no documentation. Help please?

Fullmental

Active member
Trying to figure out how to very quickly build a script via REST API or something similar that can generate a batch of XFMG albums and media items, and it looks like all that documentation is empty in the API endpoints guide. I don't see much on XFMG in general in the developer docs either.

If I had a list of locally hosted images, post IDs they belong to, and a replacement string for me to run against the post in the db after the fact, how do I structure the API call to populate a media album for that post, and insert each locally hosted image into that album? My assumption is I start by having an album category (either create or use existing), create an album for that post ID, attempt to upload the new media item to the album, get the view URL of the album, and use that in my string replacement query to place the album URL where I want it. But I can't even seem to get a media item to post via the API so far. I can get an album created, but I can't populate said album without a working media POST function.

Using a GET request for the media item by ID I see the following structure, but obviously that's a lot of info and I don't know what might be auto generated, what's optional, what's required, etc., when making a corresponding POST request.

Media
-Album
--User
--add_privacy
--album_id
--album_state
--can_add
--can_edit
--can_hard_delete
--can_react
--can_soft_delete
--category_id
--comment_count
--create_date
--description
--is_reacted_to
--is_watching
--last_comment_date
--last_comment_id
--last_comment_user_id
--last_comment_username
--last_update_date
--media_count
--rating_avg
--rating_count
--rating_weighted
--reaction_score
--thumbnail_url
--title
--user_id
--username
--view_count
--view_privacy
--view_url
--warning_message
-User
-album_id
-can_edit
-can_edit_tags
-can_hard_delete
-can_react
-can_soft_delete
-category_id
-comment_count
-custom_fields
-description
-file_size
-height
-is_reacted_to
-is_watching
-last_comment_date
-last_comment_id
-last_comment_user_id
-last_comment_username
-last_edit_date
-media_date
-media_id
-media_state
-media_type
-media_url
-rating_avg
-rating_count
-rating_weighted
-reaction_score
-tags
-thumbnail_url
-title
-user_id
-username
-view_count
-view_url
-warning_message
-width

My assumption is the following items are required to make a new media item:
Code:
title
description
album_id

However, I have no idea what to assign the actual local file to. When I make a POST request with just this info I think I need and a generic "file" definition pointing to the local server path of a test image, all I get is a null response. No information on why it was null, no direction, nothing, so it won't even tell me if there's a missing required element. I even tried using the view_url field to point to a local file, even though I'm pretty sure that's not what it's meant for, and still nothing.

Here's an example of the python code I'm testing with to make the post request. API info such as the key and base URL are referenced in a private class elsewhere, but those are confirmed working as I can take the same structure and make the GET request by media ID. The album ID already exists and the user ID (as well as the API key account) has required permissions to manage the XF media gallery. Additionally, the user the key is associated with owns the album specified in the code so they should have add permissions whichever way you slice it:

Code:
def post_media_item(apiKey, baseURL, contentURL):
    headers = {"XF-Api-Key": apiKey}
 
    # construct dummy media item
    media_item_data = {
        "title": "APITEST",
        "description": "Attempted API call to POST a new media item to an album.",
        "view_url": contentURL,
        "album_id": "339",
        "user_id": "1"
    }
 
    reponse =  r = requests.post(url = URL+"media", headers = HEADER, data=media_item_data)

When I run a get on the album I'm trying to post to, I can confirm the user the API key is associated with can view, add, and edit:

"album_state": "visible",
"can_add": true,
"can_edit": true,

EDIT: I found the mediaitem.php script in the XFMG API folder, trying to see what the construct is for the post parameter. It seems to only check permission, check for spam, and then send it along to a MediaItem entity. Followed that, found the setupApiResultData function which seems to be the only thing that looks remotely related to what I'm looking for. There's a switch statement inside that checks if the media type is an embed or not, and it uses a "media_url" string for the evaluation. Tried that on the API to point to the server file, still nothing. Kind of at a loss here? I would expect this bit of code would handle the attachment process based on the media_url, yes?

if ($this->media_type == 'embed')
{
$result->media_embed_url = $this->media_embed_url;
}
else
{
switch ($this->media_type)
{
case 'audio':
$result->media_url = $this->getAudioUrl(true);
break;

case 'video':
$result->media_url = $this->getVideoUrl(true);
break;

default:
$result->media_url = $this->app()->router('api')->buildLink('canonical:media/data', $this);
}

$attachment = $this->Attachment;

$result->file_size = $attachment->file_size;
$result->height = $attachment->Data->height;
$result->width = $attachment->Data->width;
}

EDIT 2: OK, I'm dumb. I wasn't returning the JSON data to the print statement I made for debugging. Of course.

I'm seeing the following response now:

Code:
{
    "errors": [
        {
            "code": "required_input_missing",
            "message": "Required input missing: embed_url, file",
            "params": {
                "missing": [
                    "embed_url",
                    "file"
                ]
            }
        }
    ]
}

I updated my code as follows to see if adding a file would help:

Code:
def post_media_item(apiKey, baseURL, contentURL):
 
    # construct dummy media item
    media_item_data = {
        "title": "APITEST",
        "description": "Attempted API call to POST a new media item to an album.",
        "file": contentURL,
        "album_id": "339",
        "user_id": "1"
    }
 
    reponse =  r = requests.post(url = URL+"media", headers = HEADER, data=media_item_data)

However, when updating the code to use a "file" element, the message stays the same, required input missing: embed_url, file.

Tried an "embed_URL" to a random image URL and it does work, so that's progress!

But why doesn't it recognize the file? I added a statement to confirm the test file can be opened from the same script, and attributes such as dimensions and image type can be read using PIL, so I know the file definition is valid. It just doesn't seem to like receiving the "file" element.[/img][/spoiler]
 
Last edited:
This just gets weirder and weirder.

If anyone needs an answer to the file portion, I might have figured it out? Apparently you can pass the "file" element as a "files" parameter in the request, not as a path to the file in the data parameter. The file seemingly needs to be encoded as a binary, and you pass it like this: requests.post(url = URL+"media", headers = HEADER, data=media_item_data, files = imageFile).

However, now it's reporting a different issue. It claims the album_id and category_id are also missing. Didn't report that before! I was already passing the album_id, but I added the category_id too and, it still thinks its not getting that data:

Code:
def post_media_item(apiKey, baseURL, contentURL):
    
    # construct dummy media item
    mediaItemData = {
        "title": "APITEST",
        "description": "Attempted API call to POST a new media item to an album.",
        "user_id": "1",
        "album_id": "339",
        "category_id": "4"
    }
    print("constructed dummy media item with the following attributes:")
    print(mediaItemData)
    with open(contentURL, 'rb') as file:
        imageFile = {'file': (os.path.basename(contentURL), file)}
        r = requests.post(url = baseURL+"media", headers = apiKey, data=mediaItemData, files = imageFile)
    
    return r.json()

The console output is as follows, including print statements to confirm the media_item_data object does in fact have the parameters listed:

Code:
constructed dummy media item with the following attributes:
{'title': 'APITEST', 'description': 'Attempted API call to POST a new media item to an album.', 'user_id': '1', 'album_id': 339, 'category_id': 4}
{
    "errors": [
        {
            "code": "required_input_missing",
            "message": "Required input missing: album_id, category_id",
            "params": {
                "missing": [
                    "album_id",
                    "category_id"
                ]
            }
        }
    ]
}

Is there a specific order or hierarchy that needs to be followed? It didn't seem to matter when creating the album from the API.
 
Still looking for some help trying to figure out how to post a file as an API request with XFMG. Anyone try before or have an idea of where to look? I have tried structuring the data in various ways, including as encoded json data, file data, query parameters, and more. I always get the same two errors, either "embed_url"/"file" are missing, or "album_id" and "category_id" are missing. It always switches between the two, never reporting both in the same response. At this point I wonder if this part of the API works at all. Has anyone ever found success with it?
 
Last edited:
I literally have no idea what changed, but it works now. I did a server reboot at some point, maybe something wasn't working? IDK. Anyway, the code's barely changed other than to clean up a few small things. The core funcitonality is exactly the same so I have no idea why it suddenly started to function as intended.

Code:
def post_media_item(apiKey, baseURL, uploaded_file):
    
    # construct dummy media item
    mediaItemData = {
        "title": "APITEST",
        "description": "Attempted API call to POST a new media item to an album.",
        "album_id": 341
    }
    with open(uploaded_file, 'rb') as file:
        mediaItemFile = (os.path.basename(uploaded_file), file)
        r = requests.post(url=baseURL+"media", headers=apiKey, data=mediaItemData, files={'file': mediaItemFile})
    
    return r.json()

You can use this boilerplate code to get started if you're running into the same issue. I would recommend adding some validation and error checking before pushing to any production site, but this should do the job for a basic test function. Just call it with your header, API url, and the path to a file you want to upload.
 
Last edited:
Top Bottom