Кабинет
API

#Запись

Тело запроса — JSON. Ключи объекта — коды полей справочника, те же, что показывает /fields/.

#Завести запись

curl -X POST \
     -H "Authorization: Bearer КЛЮЧ" \
     -H "Content-Type: application/json" \
     -d '{"title": "Новая статья", "content": "<p>Текст</p>"}' \
     https://ваш-сайт.ru/api/v1/lists/4/records/
<?php
$data = ['title' => 'Новая статья', 'content' => '<p>Текст</p>'];

$ch = curl_init('https://ваш-сайт.ru/api/v1/lists/4/records/');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POSTFIELDS     => json_encode($data, JSON_UNESCAPED_UNICODE),
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer КЛЮЧ',
        'Content-Type: application/json',
    ],
]);
$answer = json_decode(curl_exec($ch), true);
curl_close($ch);

echo 'номер записи: ', $answer['id'], PHP_EOL;
if ($answer['skipped']) {
    echo 'не записаны поля: ', implode(', ', $answer['skipped']), PHP_EOL;
}
import requests

r = requests.post(
    'https://ваш-сайт.ru/api/v1/lists/4/records/',
    json={'title': 'Новая статья', 'content': '<p>Текст</p>'},
    headers={'Authorization': 'Bearer КЛЮЧ'},
    timeout=15,
)
answer = r.json()

print('номер записи:', answer['id'])
if answer['skipped']:
    print('не записаны поля:', ', '.join(answer['skipped']))
const res = await fetch('https://ваш-сайт.ru/api/v1/lists/4/records/', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer КЛЮЧ',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ title: 'Новая статья', content: '<p>Текст</p>' }),
});
const answer = await res.json();

console.log('номер записи:', answer.id);
if (answer.skipped.length) console.log('не записаны поля:', answer.skipped.join(', '));
{
  "ok": true,
  "id": 138,
  "skipped": [],
  "url": "https://ваш-сайт.ru/blog/novaya-statya/"
}

Ответ — 201. id — номер заведённой записи.

#Адрес записи

url — тот адрес, по которому запись открывает посетитель. Он есть, если справочник выводится страницей и у записи заполнено поле адреса.

Важно

Если адреса нет, ключа url в ответе нет вовсе. Так бывает у справочников, которые никакая страница не выводит, — «Заявки», «Статусы», «Поля форм». Пустая ссылка хуже отсутствующей: по ней нажмут и попадут никуда.

Читайте его как необязательный: answer['url'] ?? ''.

Собрать этот адрес на своей стороне нельзя: он зависит от того, какая страница выводит справочник и что записано в поле адреса. Это знает только сайт.

Заодно при записи заводится маршрут — то, по чему сайт находит запись при заходе на её адрес. Без него запись создавалась бы, а страница по своему адресу не открывалась.

PATCH отвечает так же: если поменяли адрес, url придёт новым, а маршрут перепишется.

#Поправить запись

curl -X PATCH \
     -H "Authorization: Bearer КЛЮЧ" \
     -H "Content-Type: application/json" \
     -d '{"title": "Заголовок поточнее"}' \
     https://ваш-сайт.ru/api/v1/lists/4/records/138/
<?php
$ch = curl_init('https://ваш-сайт.ru/api/v1/lists/4/records/138/');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST  => 'PATCH',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POSTFIELDS     => json_encode(['title' => 'Заголовок поточнее'],
                                          JSON_UNESCAPED_UNICODE),
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer КЛЮЧ',
        'Content-Type: application/json',
    ],
]);
$answer = json_decode(curl_exec($ch), true);
curl_close($ch);
import requests

r = requests.patch(
    'https://ваш-сайт.ru/api/v1/lists/4/records/138/',
    json={'title': 'Заголовок поточнее'},
    headers={'Authorization': 'Bearer КЛЮЧ'},
    timeout=15,
)
print(r.json())
const res = await fetch('https://ваш-сайт.ru/api/v1/lists/4/records/138/', {
  method: 'PATCH',
  headers: {
    Authorization: 'Bearer КЛЮЧ',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ title: 'Заголовок поточнее' }),
});
console.log(await res.json());

Правятся только присланные поля. Остальные остаются как были — прислать всю запись целиком не нужно. PUT работает так же.

#Удалить запись

curl -X DELETE -H "Authorization: Bearer КЛЮЧ" \
     https://ваш-сайт.ru/api/v1/lists/4/records/138/
<?php
$ch = curl_init('https://ваш-сайт.ru/api/v1/lists/4/records/138/');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST  => 'DELETE',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer КЛЮЧ'],
]);
$answer = json_decode(curl_exec($ch), true);
curl_close($ch);
import requests

r = requests.delete(
    'https://ваш-сайт.ru/api/v1/lists/4/records/138/',
    headers={'Authorization': 'Bearer КЛЮЧ'},
    timeout=15,
)
print(r.json())
const res = await fetch('https://ваш-сайт.ru/api/v1/lists/4/records/138/', {
  method: 'DELETE',
  headers: { Authorization: 'Bearer КЛЮЧ' },
});
console.log(await res.json());
{"ok": true, "id": 138}

Удаление окончательное, корзины нет.

#Картинки

Поле типа «Картинка» хранит номер файла медиатеки, а не адрес: по нему сайт собирает <picture> с webp и мобильной версией. Внешняя ссылка в такое поле не годится — на странице выйдет пустая рамка.

Сначала положите файл в медиатеку:

curl -X POST \
     -H "Authorization: Bearer КЛЮЧ" \
     -F "file=@oblozhka.png" \
     https://ваш-сайт.ru/api/v1/media/
<?php
$ch = curl_init('https://ваш-сайт.ru/api/v1/media/');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POSTFIELDS     => ['file' => new CURLFile('oblozhka.png')],
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer КЛЮЧ'],
]);
$answer = json_decode(curl_exec($ch), true);
curl_close($ch);

echo 'номер файла: ', $answer['id'], PHP_EOL;
import requests

with open('oblozhka.png', 'rb') as f:
    r = requests.post(
        'https://ваш-сайт.ru/api/v1/media/',
        files={'file': f},
        headers={'Authorization': 'Bearer КЛЮЧ'},
        timeout=60,
    )

print('номер файла:', r.json()['id'])
const form = new FormData();
form.append('file', new Blob([await readFile('oblozhka.png')]), 'oblozhka.png');

const res = await fetch('https://ваш-сайт.ru/api/v1/media/', {
  method: 'POST',
  headers: { Authorization: 'Bearer КЛЮЧ' },
  body: form,
});
const { id } = await res.json();
{
  "ok": true,
  "id": 42,
  "name": "oblozhka.png",
  "url": "/uploads/oblozhka.png",
  "files": [{"id": 42, "name": "oblozhka.png", "url": "/uploads/oblozhka.png"}]
}

Полученный id и кладите в поле картинки:

curl -X PATCH \
     -H "Authorization: Bearer КЛЮЧ" \
     -H "Content-Type: application/json" \
     -d '{"cover": "42"}' \
     https://ваш-сайт.ru/api/v1/lists/4/records/138/

#Файл уже лежит в uploads

Так бывает, когда файлы положили мимо панели — по SFTP или восстановлением из копии. Загружать их заново не нужно, достаточно завести в медиатеке:

curl -X POST \
     -H "Authorization: Bearer КЛЮЧ" \
     -H "Content-Type: application/json" \
     -d '{"path": "/uploads/blog-53.webp"}' \
     https://ваш-сайт.ru/api/v1/media/register/
{"ok": true, "id": 193, "name": "blog-53.webp",
 "url": "/uploads/blog-53.webp", "existed": false}

existed: true значит, что файл уже был в медиатеке и номер у него прежний, — повторный вызов ничего не портит и второй записи не заводит.

То же и у обычной загрузки: если прислать файл, который уже лежит в медиатеке, вернётся его номер, а копия рядом не появится. Сверка идёт по содержимому, а не по имени, — прислать тот же файл под другим именем можно, и он тоже узнается.

Принимается только имя внутри uploads: путь с .. или с вложенным каталогом отклоняется.

Важно

Файл принимается теми же правилами, что в панели: белый список расширений (jpg, jpeg, png, gif, webp, svg, pdf, doc, docx, xls, xlsx, zip), безопасное имя, миниатюры создаются сами. Расширение вне списка — отказ 422.

Почему так

Почему не ссылка на чужой сервер.

Картинка, оставшаяся у того, кто её прислал, делает сайт зависимым от чужого хозяйства: удалят там — в блоге появятся дыры, и владелец сайта узнает об этом последним. Файл в своей медиатеке не зависит ни от кого.

#Что происходит с лишними полями

Записываются только объявленные поля справочника. Всё прочее отбрасывается — но не молча: отброшенное перечислено в ответе.

{"ok": true, "id": 138, "skipped": ["author", "id"]}
Почему так

Почему отброшенное названо вслух.

Молчаливая потеря поля выглядит как поломка API, и разбираться в ней приходится тому, кто пишет программу на другом конце. Список skipped отвечает на вопрос «почему не сохранилось» сразу.

Отбрасывается три вида значений:

ЧтоПочему
id, domainслужебные: их задаёт сайт, а не тот, кто пишет
поля, которых нет в справочникеписать некуда
массивы и объектыполе справочника хранит строку

Если из присланного не осталось ни одного пригодного поля, ответ — 422 и «Нечего записывать».

#Что происходит на сайте после записи

  1. Сбрасывается кеш страниц — изменение видно сразу.
  2. Объявляется событие: api_record_created, api_record_updated или api_record_deleted. Его видно в журнале событий сайта.
Важно

Запись через API не проходит проверок формы из панели.

Обязательность полей, значения по умолчанию и подстановка адреса страницы — часть панели, а не справочника. Программа, которая пишет в API, отвечает за состав данных сама.