Коротко — потому что картинку не удаётся корректно загрузить и/или сохранить для сообщества, и в результате в wall.post передаётся пустое или неправильное attachments. Ошибки интерпретируются так:
- 15 (Access denied) — у токена/пользователя нет прав загружать/сохранять фото в этом сообществе (или вы обращаетесь к ресурсу, к которому нет доступа).
- 100 (No photo given) — при вызове photos.saveWallPhoto / wall.post не переданы корректные данные о фото (пустое поле photo / неправильно сформирован attachment).
Причины и как исправить (шаги, которые нужно проверить):
1) Правильный порядок операций
Правильная последовательность для публикации фото на стену сообщества:
- photos.getWallUploadServer (передать group_id) → получить upload_url
- POST самого файла на upload_url (multipart/form-data)
- получить ответ {server, photo, hash}
- photos.saveWallPhoto (передать group_id, server, photo, hash) → получите id и owner_id сохранённого фото
- wall.post (owner_id = -GROUP_ID, from_group = 1, attachments = "photo{owner_id}_{id}")
Если где-то внутри этого потока что-то вернуло ошибку/пустоту, фото не появится и появится ошибка 100.
2) Токен и права
- Для публикации фото в сообщество лучше использовать токен сообщества (полученный в настройках сообщества) или пользовательский токен администратора сообщества с правами на управление сообществом.
- Токен должен иметь права/photos и wall (и, если нужно, groups). Если вы используете пользовательский токен, пользователь должен быть админом сообщества.
- Ошибка 15 часто означает, что вы пытаетесь вызвать photos.saveWallPhoto с токеном, который не имеет права загружать фото в указанное сообщество (или указываете чужой group_id).
3) Правильный owner_id / group_id и знаки «-»
- При публикации в стену сообщества указывайте owner_id = -GROUP_ID в wall.post и from_group = 1, если хотите опубликовать от имени сообщества.
- При формировании attachments используйте значения, которые вернул photos.saveWallPhoto: "photo{owner_id}_{id}". owner_id там может быть отрицательным (сообщество) — используйте то, что вернул API.
- Если вы вручную конструируете строку и ошиблись со знаком, VK не найдёт фото — получится No photo given.
4) Правильная загрузка файла (multipart)
- Файл должен реально отправляться на upload_url как multipart/form-data. Убедитесь, что вы отправляете именно файл, а не пустой поток.
- Самое частое место ошибки — вы берёте неверный путь к файлу или забываете использовать CURLFile / fopen, и upload возвращает пустую photo → saveWallPhoto получает пустые параметры.
- Логируйте ответ upload_url (он должен вернуть JSON с полями server, photo, hash). Если upload возвращает ошибку — значит проблема на этапе отправки файла.
5) Примеры ошибок SDK / поля
- Некоторые SDK/примеры используют поле "photo" при multipart, некоторые — "file1". Для photos.getWallUploadServer в большинстве примеров используют поле "photo". Если не работает — проверьте, какой ключ ожидает upload_url (обычно это видно по примеру или по тестовой CURL).
- В vk-php-sdk вы можете использовать Guzzle для отправки multipart: name => 'photo', contents => fopen($path, 'r').
Пример последовательности на PHP (vkcom/vk-php-sdk + Guzzle):
$vk = new \VK\Client\VKApiClient();
$token = 'COMMUNITY_ACCESS_TOKEN';
$groupId = 12345;
// 1) Получаем upload_url
$uploadInfo = $vk->photos()->getWallUploadServer($token, ['group_id' => $groupId]);
// 2) Загружаем файл на полученный URL
$client = new \GuzzleHttp\Client();
$response = $client->post($uploadInfo['upload_url'], [
'multipart' => [
[
'name' => 'photo',
'contents' => fopen('/path/to/file.jpg', 'r'),
'filename' => 'file.jpg'
]
]
]);
$uploadResult = json_decode((string)$response->getBody(), true);
// 3) Сохраняем фото
$save = $vk->photos()->saveWallPhoto($token, [
'group_id' => $groupId,
'photo' => $uploadResult['photo'],
'server' => $uploadResult['server'],
'hash' => $uploadResult['hash']
]);
$att = 'photo' . $save[0]['owner_id'] . '_' . $save[0]['id'];
// 4) Публикуем на стене
$vk->wall()->post($token, [
'owner_id' => -$groupId,
'from_group' => 1,
'message' => 'Текст',
'attachments' => $att
]);
6) Отладка — что логировать прямо сейчас
- Ответ photos.getWallUploadServer
- HTTP-ответ от upload_url (вывод тела)
- Ответ photos.saveWallPhoto
- Входные параметры в wall.post
Часто видно, что upload возвращает пустой photo или ошибку — отсюда и 100.
7) Прочие нюансы
- Убедитесь, что версия API (v) передаётся и актуальна.
- Если вы используете CRON/PHP CLI, убедитесь, что у процесса есть доступ к файлам и не ломается open_basedir.
- Если в группе включены ограничения по публикациям (например, запрет для API) — это тоже может давать 15.
Итого: проверьте права токена (рекомендуется токен сообщества), убедитесь, что вы корректно POSTите файл на upload_url и что photos.saveWallPhoto возвращает данные (owner_id и id). Ошибки 15 и 100 — следствие либо отсутствия прав, либо пустого/неправильного ответа на этапе загрузки файла.
Если хотите, пришлите: ваш фрагмент кода (как вы делаете upload на upload_url и как вызываете saveWallPhoto / wall.post), ответы API (лог) — посмотрю и укажу конкретную строку, где ошибка.