使用wp_remote_post()将带有图像的多部分表单数据发布到iNaturalist REST API

I'm developing a WordPress website that allows users to submit observations that are then pushed to the iNaturalist API. I have successfully authenticated, pushed the observation data, and received the ID of the observation from the API. I am completely hung up on pushing a photo with the POST /observation_photos endpoint. My suspicion is that I'm not formatting the payload correctly, but I can't find any working examples of this type of request in a language I'm familiar with, so there's a lot of blind stabbing in the dark.

I've tried a lot of things before posting here so bear with me as I go through them and the results of each.

First, I tried the following:

$photo_payload = [
    'method' => 'POST',
    'timeout' => 10,
    'headers' => [
        'Authorization' => "Bearer $auth",
        'Content-Type' => "multipart/form-data;"
    ],
    'body' => [
        'observation_photo' => [
            'observation_id' => $inat_id
        ],
        'file' =>file_get_contents($photo_sized)
    ]
];
$post_photo = wp_remote_post($inat_base_url . '/observation_photos', $photo_payload);

Which results in: 500 Internal Server Error

Then I tried removing the file from the body array and just send:

$photo_payload = [
    'method' => 'POST',
    'timeout' => 10,
    'headers' => [
        'Authorization' => "Bearer $auth",
        'Content-Type' => "multipart/form-data;"
    ],
    'body' => [
        'observation_photo' => [
            'observation_id' => $inat_id
        ],
        'file' => ''
    ]
];

We get a success and iNaturalist adds a placeholder "Processing..." image to the observation (as seen here: https://www.inaturalist.org/observations/8014211).

So that tells me that the file data is not being sent properly. I tried base64_encoding the image data but got a 500 error for that too.

Next up was this attempt:

$photo_payload = [
    'method' => 'POST',
    'timeout' => 10,
    'headers' => [
        'Authorization' => "Bearer $auth",
        'Content-Type' => "multipart/form-data;"
    ],
    'body' => [
        'observation_photo' => [
            'observation_id' => $inat_id
        ],
        'file' => '@' . $photo_sized . ';filename=' . basename($photo_sized) . ';type=' . get_post_mime_type($photo_id)
    ]
];

That gave me another 500 Internal Server Error.

Then I started down the multipart/form-data rabbit hole and based on this post (http://codechutney.com/posting-file-using-wp_remote_post/) tried:

$boundary = wp_generate_password( 24 );

$body = '';
$body .= '--' . $boundary . "
";
$body .= 'Content-Disposition: form-data; name="file"; filename="' . basename($photo_sized) . "\"
";
$body .= 'Content-Type: ' . $photo_type . "

";
$body .= base64_encode(file_get_contents($photo_sized)) . "
";
$body .= '--' . $boundary . "
";
$body .= 'Content-Disposition: form-data; name="observation_photo"' . "
";
$body .= 'Content-Type: application/json' . "

";
$body .= json_encode(['observation_id' => $inat_id]) . "
";
$body .= '--' . $boundary . '--' . "
";

$photo_payload = [
    'method' => 'POST',
    'timeout' => 10,
    'headers' => [
        'Authorization' => "Bearer $auth",
        'Content-Type' => "multipart/form-data; boundary=$boundary"
    ],
    'body' => $body
];

Alas, another 500 Internal Server Error.

Seriously, I have been banging my head against the wall on this for so long that I can't remember all the things I tried. At some point, I know I was getting timeout errors too, but I can't recreate them right now.

Other notes: It doesn't seem to change anything if I change the endpoint from observation_photos to observation_photos.json. The file that I am trying to send is 7585 bytes.

Heard back from a developer at iNaturalist! They let me know the proper formatting of the multipart request is as follows:

[body] => --abc
Content-Disposition: form-data; name="file"; filename="20170816_071116-150x150.jpg"
Content-Type: image/jpeg

[raw image data]
--abc
Content-Disposition: form-data; name="observation_photo[observation_id]"

8014211
--abc--

So I changed the body of the payload to the following and it works!

$body = '';
$body .= '--' . $boundary . "
";
$body .= 'Content-Disposition: form-data; name="file"; filename="' . basename($photo_sized) . "\"
";
$body .= 'Content-Type: ' . $photo_type . "

";
$body .= base64_encode(file_get_contents($photo_sized)) . "
";
$body .= '--' . $boundary . "
";
$body .= 'Content-Disposition: form-data; name="observation_photo[observation_id]"' . "
";
$body .= 'Content-Type: application/json' . "

";
$body .= $inat_id . "
";
$body .= '--' . $boundary . '--' . "
";

Here's a link to the solution on GitHub issues: https://github.com/inaturalist/inaturalist/issues/1483#issuecomment-331239325