Перенеси свои фильмы
на ember
Отметки просмотренного, оценки и списки — из любого приложения, с любой площадки. Один ключ, один запрос: пришли ID, ссылку или просто название — разберёмся сами.
Начало
API — это способ сказать ember «этот фильм просмотрен» из любого другого места: из приложения, которым ты смотришь кино, из выгрузки другого сервиса, из своего скрипта.
PUT /api/v1/entries
найдём фильм по ID,ссылке или названию
Фильм или игру при этом можно указать как угодно — imdb_id, steam_appid, ссылка на Кинопоиск, Steam или Trakt, свой внутренний ID приложения, просто название с годом (для игр добавь "media_type": "game"). Следующий раздел — про это.
API-ключ
Создай ключ в настройках → API-ключи. Показывается один раз — нажми на него, и он скопируется.
Подставляй его в заголовок Authorization каждого запроса:
Authorization: Bearer ember_xxxxxxxx
Вместо Authorization можно слать заголовок X-Api-Key: ember_xxxxxxxx — это то же самое.
Эндпоинты
Список эндпоинтов. Готовые примеры на Python и curl: скопируй, подставь свой ключ и домен.
GET/api/v1/meпроверка ключа — кто ты и какой токен
import requests
API = "https://ТВОЙ-САЙТ/api/v1"
KEY = "ember_xxxxxxxx"
H = {"Authorization": f"Bearer {KEY}"}
r = requests.get(f"{API}/me", headers=H)
print(r.json())
curl.exe "https://ТВОЙ-САЙТ/api/v1/me" -H "Authorization: Bearer ember_xxxxxxxx"
GET/api/v1/lookup?imdb_id=tt0110912найти фильм, ничего не записывая — проверить сопоставление
import requests
API = "https://ТВОЙ-САЙТ/api/v1"
KEY = "ember_xxxxxxxx"
H = {"Authorization": f"Bearer {KEY}"}
r = requests.get(f"{API}/lookup", headers=H,
params={"imdb_id": "tt0110912"})
print(r.json())
curl.exe "https://ТВОЙ-САЙТ/api/v1/lookup?imdb_id=tt0110912" -H "Authorization: Bearer ember_xxxxxxxx"
GET/api/v1/search?q=матрицапоиск по каталогу ember
import requests
API = "https://ТВОЙ-САЙТ/api/v1"
KEY = "ember_xxxxxxxx"
H = {"Authorization": f"Bearer {KEY}"}
r = requests.get(f"{API}/search", headers=H,
params={"q": "матрица"})
print(r.json())
curl.exe "https://ТВОЙ-САЙТ/api/v1/search?q=матрица" -H "Authorization: Bearer ember_xxxxxxxx"
GET/api/v1/entriesтвоя библиотека: ?status=watched|want|watching|rated, ?since=, ?limit=&offset=
import requests
API = "https://ТВОЙ-САЙТ/api/v1"
KEY = "ember_xxxxxxxx"
H = {"Authorization": f"Bearer {KEY}"}
r = requests.get(f"{API}/entries", headers=H,
params={"status": "watched", "limit": 50})
print(r.json())
curl.exe "https://ТВОЙ-САЙТ/api/v1/entries?status=watched" -H "Authorization: Bearer ember_xxxxxxxx"
PUT/api/v1/entriesсоздать или обновить запись — главная точка входа
import requests
API = "https://ТВОЙ-САЙТ/api/v1"
KEY = "ember_xxxxxxxx"
H = {"Authorization": f"Bearer {KEY}"}
r = requests.put(f"{API}/entries", headers=H, json={
"imdb_id": "tt0133093",
"status": "watched",
"rating": 8.5,
"watched_on": "2026-10-03",
})
print(r.json())
curl.exe -X PUT "https://ТВОЙ-САЙТ/api/v1/entries" -H "Authorization: Bearer ember_xxxxxxxx" -H "Content-Type: application/json" -d '{"imdb_id":"tt0133093","status":"watched","rating":8.5}'
POST/api/v1/entries/batchпакет до 100 записей — массовый перенос
import requests
API = "https://ТВОЙ-САЙТ/api/v1"
KEY = "ember_xxxxxxxx"
H = {"Authorization": f"Bearer {KEY}"}
r = requests.post(f"{API}/entries/batch", headers=H,
json={"items": [
{"imdb_id": "tt0133093", "status": "watched", "rating": 8.5},
{"url": "https://letterboxd.com/film/parasite/", "status": "want"},
{"title": "Мой личный фильм", "allow_manual": True},
]})
print(r.json())
curl.exe -X POST "https://ТВОЙ-САЙТ/api/v1/entries/batch" -H "Authorization: Bearer ember_xxxxxxxx" -H "Content-Type: application/json" -d '{"items":[{"imdb_id":"tt0133093","status":"watched","rating":8.5}]}'
DELETE/api/v1/entries/{kp_id}удалить запись из библиотеки
import requests
API = "https://ТВОЙ-САЙТ/api/v1"
KEY = "ember_xxxxxxxx"
H = {"Authorization": f"Bearer {KEY}"}
r = requests.delete(f"{API}/entries/326", headers=H)
print(r.status_code) # 204 — удалено
curl.exe -X DELETE "https://ТВОЙ-САЙТ/api/v1/entries/326" -H "Authorization: Bearer ember_xxxxxxxx"
Как сослаться на фильм
Разбор примера готового запроса
import requests
API = "https://ТВОЙ-САЙТ/api/v1"
KEY = "ember_xxxxxxxx"
H = {"Authorization": f"Bearer {KEY}"}
r = requests.put(f"{API}/entries", headers=H, json={
"imdb_id": "tt0133093", 1
"status": "watched", 2
"rating": 8.5, 3
"watched_on": "2026-10-03", 4
"allow_manual": True, 5
"poster": "https://…/cover.jpg", 6
})
print(r.json())
Как указать фильм — любой из вариантов
Пришли что есть — API сам выберет лучший вариант по приоритету:
kp_idнадёжнее всего
ID Кинопоиска: 326 → «Побег из Шоушенка». Работает всегда, даже для фильма, которого ещё нет на ember.
imdb_idуниверсальный
tt0110912 → «Криминальное чтиво». Работает для любого фильма мира: если его нет в базе ember, название и год подтянутся из открытого каталога, а фильм найдётся на Кинопоиске с проверкой imdbId.
urlпроще всего
Просто ссылка на страницу фильма: kinopoisk.ru, imdb.com, letterboxd.com, themoviedb.org, trakt.tv. ID вытащится из самой ссылки.
title + yearзапасной
Поиск по названию — для экспортов, где нет никаких ID (например, CSV из Letterboxd). year можно не передавать — без него найдём по одному названию, но с годом точнее.
trakt_idtrakt
23456 — числовой ID Trakt. Резолвится через их API в imdb_id и запоминается навсегда: дальше работает мгновенно.
externalсвой id
{"source": "myapp", "id": "42"} — ID с твоей площадки. При первом запросе разрешается по остальным полям, а соответствие запоминается: дальше он работает мгновенно. Название source придумай сам — это неймспейс твоей интеграции.
"status" — что записать
"watched"просмотреноФильм просмотрен (с оценкой или без), убирается из «хочу посмотреть».
"want"хочуВ избранное со статусом «хочу посмотреть».
"watching"смотрюВ избранное со статусом «смотрю сейчас».
"none"убратьУдалить запись из библиотеки целиком.
"rating" — твоя оценка
Число от 0 до 10. null снимает оценку — фильм при этом остаётся просмотренным. Оценка сама по себе отмечает фильм просмотренным, даже без "status".
"watched_on" — когда посмотрел
Дата просмотра в формате ГГГГ-ММ-ДД. Попадёт в дневник на сайте — при переносе истории из другого сервиса даты сохранятся как были.
"allow_manual" — если фильма нет нигде
true — если фильм не нашёлся ни в одном каталоге, вместо ошибки создаётся ручная карточка (как «Свой фильм» на сайте). При массовом переносе ни одна строка не потеряется. Работает только на запись.
"poster" — обложка ручной карточки
Ссылка на картинку (http/https). Применяется, только когда создаётся ручная карточка — то есть вместе с "allow_manual": true и ненайденным фильмом. Чужие постеры не перезаписывает.
В каждом ответе поле resolved_via показывает, как фильм нашёлся: kp_id, imdb:cinemeta, url:kinopoisk, external:myapp, title, manual…
Отметить фильм (upsert)
PUT /api/v1/entries
Authorization: Bearer ember_xxxxxxxx
{
"imdb_id": "tt0133093",
"status": "watched",
"rating": 8.5,
"watched_on": "2026-10-03",
// опционально:
"allow_manual": true,
"poster": "https://…" // обложка ручной карточки
}
{
"ok": true,
"resolved_via": "imdb:cinemeta",
"entry": {
"work": {
"kp_id": 326,
"title": "Матрица",
"year": 1999
},
"status": "watched",
"watched": true,
"score": 8.5,
"watched_on": "2026-10-03"
}
}
Что значит status
"watched"просмотреноФильм просмотрен (с оценкой или без), убирается из «хочу посмотреть».
"want"хочуВ избранное со статусом «хочу посмотреть».
"watching"смотрюВ избранное со статусом «смотрю сейчас».
"none"убратьУдалить запись из библиотеки целиком.
rating: 0–10; null снимает оценку (просмотренным фильм остаётся). Оценка сама по себе отмечает фильм просмотренным. watched_on — дата просмотра в формате ГГГГ-ММ-ДД. Повторный запрос — идемпотентен: просто обновит поля.
Массовый перенос
До 100 фильмов за запрос. Каждый элемент — как в upsert. Ошибка в одной строке не роняет остальные.
import requests
API = "https://ТВОЙ-САЙТ/api/v1"
KEY = "ember_xxxxxxxx"
H = {"Authorization": f"Bearer {KEY}"}
r = requests.post(f"{API}/entries/batch", headers=H, json={
"items": [
{"imdb_id": "tt0133093", "status": "watched", "rating": 8.5},
{"url": "https://letterboxd.com/film/alien/", "status": "watched"},
{"title": "Город грехов", "year": 2005, "status": "want"},
]
})
print(r.json())
curl.exe -X POST "https://ТВОЙ-САЙТ/api/v1/entries/batch" -H "Authorization: Bearer ember_xxxxxxxx" -H "Content-Type: application/json" -d '{"items":[{"imdb_id":"tt0133093","status":"watched","rating":8.5},{"title":"Город грехов","year":2005,"status":"want"}]}'
Что придёт в ответ:
{
"ok": true,
"total": 4,
"applied": 4,
"aborted": false,
"results": [
{"index": 0, "ok": true,
"resolved_via": "imdb:cinemeta", …},
{"index": 1, "ok": true,
"resolved_via": "url:letterboxd", …},
…
]
}
У ошибок бывает "retryable": true — значит, кончилась дневная квота Кинопоиска или временный сбой: повтори позже, успешные строки не потеряются. Если обработка не уложилась в дедлайн, в ответе будет "aborted": true, а остатку — ошибка deadline: просто отправь необработанные элементы повторным пакетом.
Ошибки и лимиты
Формат ошибки: {"ok": false, "error": {"code": "…", "message": "…"}}
unauthorized401Ключ не передан, отозван или аккаунт отключён.
validation_error400 / 413Кривые поля: плохой status, рейтинг вне 0–10, нечитаемый JSON, слишком длинные строки (title ≤ 300, url ≤ 2048). 413 — тело больше 256 КБ.
unsupported_url400Ссылка с неизвестного домена — пришли imdb_id или название.
not_found / resolution_failed404Фильм не сопоставился. С retryable: true — повтори позже.
manual_limit400Исчерпана суточная квота ручных карточек (200/день) — это защита каталога от спама.
deadlineв batchПакет оборван по дедлайну — элемент не обработан, отправь его повторным пакетом.
rate_limited429Превышен лимит — подожди и повтори. Вернётся и при флуде невалидными ключами.
Лимиты
Лимиты считаются на пользователя, а не на ключ — несколько ключей общую квоту не увеличивают.
Перенос фильмов
Существует много способов перенести фильмы с других площадок на ember:
CSV-файл
Вся библиотека разом — оценки, даты просмотров, списки «хочу» и «смотрю».
- Выгрузи CSV — инструкции для каждой площадки прилагаются
- Загрузи его — и мы сопоставим сами
Закладка
Фильм добавляется в один клик прямо со страницы Кинопоиска, IMDb или Letterboxd.
+ в emberКак добавить на компьютере
- Покажи панель закладок: Ctrl+Shift+B, в Safari — ⌘+Shift+B («Вид → Показать панель избранного»).
- Зажми кнопку «+ в ember» выше и перетащи её на эту панель.
- Готово — она останется там навсегда.
Как пользоваться: открыл страницу фильма на Кинопоиске, IMDb или Letterboxd → жмёшь закладку + в ember → тебя переносит на ember, фильм уже найден → жми «Добавить».
Добавление по ссылке
Один фильм за раз — прямо отсюда, без переходов:
Разработчикам: то же самое автоматизируется через API — ключ в настройках, готовые сниппеты в «Примеры кода».
Примеры кода
Все возможности API в готовых запросах — выбери категорию и язык, скопируй, подставь домен и ключ.
GET /api/v1/meimport requests
API = "https://ТВОЙ-САЙТ/api/v1"
KEY = "ember_xxxxxxxx"
H = {"Authorization": f"Bearer {KEY}"}
r = requests.get(f"{API}/me", headers=H)
print(r.json())
curl.exe "https://ТВОЙ-САЙТ/api/v1/me" -H "Authorization: Bearer ember_xxxxxxxx"
PUT /api/v1/entriesimport requests
API = "https://ТВОЙ-САЙТ/api/v1"
KEY = "ember_xxxxxxxx"
H = {"Authorization": f"Bearer {KEY}"}
r = requests.put(f"{API}/entries", headers=H, json={
"imdb_id": "tt0133093",
"status": "watched",
"rating": 8.5,
"watched_on": "2026-10-03",
})
print(r.json())
curl.exe -X PUT "https://ТВОЙ-САЙТ/api/v1/entries" -H "Authorization: Bearer ember_xxxxxxxx" -H "Content-Type: application/json" -d '{"imdb_id":"tt0133093","status":"watched","rating":8.5,"watched_on":"2026-10-03"}'
PUT /api/v1/entriesimport requests
API = "https://ТВОЙ-САЙТ/api/v1"
KEY = "ember_xxxxxxxx"
H = {"Authorization": f"Bearer {KEY}"}
r = requests.put(f"{API}/entries", headers=H, json={
"url": "https://letterboxd.com/film/parasite/",
"status": "want",
})
print(r.json())
curl.exe -X PUT "https://ТВОЙ-САЙТ/api/v1/entries" -H "Authorization: Bearer ember_xxxxxxxx" -H "Content-Type: application/json" -d '{"url":"https://letterboxd.com/film/parasite/","status":"want"}'
PUT /api/v1/entriesimport requests
API = "https://ТВОЙ-САЙТ/api/v1"
KEY = "ember_xxxxxxxx"
H = {"Authorization": f"Bearer {KEY}"}
r = requests.put(f"{API}/entries", headers=H, json={
"kp_id": 326,
"status": "watching",
})
print(r.json())
curl.exe -X PUT "https://ТВОЙ-САЙТ/api/v1/entries" -H "Authorization: Bearer ember_xxxxxxxx" -H "Content-Type: application/json" -d '{"kp_id":326,"status":"watching"}'
PUT /api/v1/entriesimport requests
API = "https://ТВОЙ-САЙТ/api/v1"
KEY = "ember_xxxxxxxx"
H = {"Authorization": f"Bearer {KEY}"}
r = requests.put(f"{API}/entries", headers=H, json={
"url": "https://www.kinopoisk.ru/film/326/",
"status": "watched",
"rating": 9,
})
print(r.json())
curl.exe -X PUT "https://ТВОЙ-САЙТ/api/v1/entries" -H "Authorization: Bearer ember_xxxxxxxx" -H "Content-Type: application/json" -d '{"url":"https://www.kinopoisk.ru/film/326/","status":"watched","rating":9}'
PUT /api/v1/entriesimport requests
API = "https://ТВОЙ-САЙТ/api/v1"
KEY = "ember_xxxxxxxx"
H = {"Authorization": f"Bearer {KEY}"}
r = requests.put(f"{API}/entries", headers=H, json={
"title": "Матрица",
"year": 1999, # year можно опустить
"status": "watched",
})
print(r.json())
curl.exe -X PUT "https://ТВОЙ-САЙТ/api/v1/entries" -H "Authorization: Bearer ember_xxxxxxxx" -H "Content-Type: application/json" -d '{"title":"Матрица","year":1999,"status":"watched"}'
PUT /api/v1/entriesimport requests
API = "https://ТВОЙ-САЙТ/api/v1"
KEY = "ember_xxxxxxxx"
H = {"Authorization": f"Bearer {KEY}"}
r = requests.put(f"{API}/entries", headers=H, json={
"external": {"source": "myapp", "id": "42"},
"title": "Обитель зла",
"year": 2002,
"status": "watched",
})
print(r.json())
curl.exe -X PUT "https://ТВОЙ-САЙТ/api/v1/entries" -H "Authorization: Bearer ember_xxxxxxxx" -H "Content-Type: application/json" -d '{"external":{"source":"myapp","id":"42"},"title":"Обитель зла","year":2002,"status":"watched"}'
Соответствие myapp:42 → фильм запомнится: дальше можно слать один external.
PUT /api/v1/entriesimport requests
API = "https://ТВОЙ-САЙТ/api/v1"
KEY = "ember_xxxxxxxx"
H = {"Authorization": f"Bearer {KEY}"}
r = requests.put(f"{API}/entries", headers=H, json={
"title": "Мой личный фильм о котиках",
"year": 2024,
"status": "watched",
"allow_manual": True,
"poster": "https://example.com/cover.jpg",
})
print(r.json())
curl.exe -X PUT "https://ТВОЙ-САЙТ/api/v1/entries" -H "Authorization: Bearer ember_xxxxxxxx" -H "Content-Type: application/json" -d '{"title":"Мой личный фильм о котиках","year":2024,"status":"watched","allow_manual":true,"poster":"https://example.com/cover.jpg"}'
Фильм не нашёлся нигде → вместо ошибки создаётся ручная карточка.
GET /api/v1/lookupimport requests
API = "https://ТВОЙ-САЙТ/api/v1"
KEY = "ember_xxxxxxxx"
H = {"Authorization": f"Bearer {KEY}"}
r = requests.get(f"{API}/lookup", headers=H,
params={"imdb_id": "tt0110912"})
print(r.json())
curl.exe "https://ТВОЙ-САЙТ/api/v1/lookup?imdb_id=tt0110912" -H "Authorization: Bearer ember_xxxxxxxx"
Ничего не записывает — только показывает, что за фильм найдётся.
GET /api/v1/searchimport requests
API = "https://ТВОЙ-САЙТ/api/v1"
KEY = "ember_xxxxxxxx"
H = {"Authorization": f"Bearer {KEY}"}
r = requests.get(f"{API}/search", headers=H,
params={"q": "матрица"})
print(r.json())
curl.exe "https://ТВОЙ-САЙТ/api/v1/search?q=матрица" -H "Authorization: Bearer ember_xxxxxxxx"
GET /api/v1/entriesimport requests
API = "https://ТВОЙ-САЙТ/api/v1"
KEY = "ember_xxxxxxxx"
H = {"Authorization": f"Bearer {KEY}"}
r = requests.get(f"{API}/entries", headers=H,
params={"status": "watched", "limit": 50})
print(r.json())
curl.exe "https://ТВОЙ-САЙТ/api/v1/entries?status=watched&limit=50" -H "Authorization: Bearer ember_xxxxxxxx"
Статусы для фильтра: watched, want, watching, rated.
GET /api/v1/entries?since=…import requests
API = "https://ТВОЙ-САЙТ/api/v1"
KEY = "ember_xxxxxxxx"
H = {"Authorization": f"Bearer {KEY}"}
r = requests.get(f"{API}/entries", headers=H,
params={"since": "2026-01-01"})
print(r.json())
curl.exe "https://ТВОЙ-САЙТ/api/v1/entries?since=2026-01-01" -H "Authorization: Bearer ember_xxxxxxxx"
POST /api/v1/entries/batchimport requests
API = "https://ТВОЙ-САЙТ/api/v1"
KEY = "ember_xxxxxxxx"
H = {"Authorization": f"Bearer {KEY}"}
r = requests.post(f"{API}/entries/batch", headers=H,
json={"items": [
{"imdb_id": "tt0133093", "status": "watched", "rating": 8.5},
{"url": "https://letterboxd.com/film/parasite/", "status": "want"},
{"title": "Мой личный фильм", "allow_manual": True},
]})
print(r.json())
curl.exe -X POST "https://ТВОЙ-САЙТ/api/v1/entries/batch" -H "Authorization: Bearer ember_xxxxxxxx" -H "Content-Type: application/json" -d '{"items":[{"imdb_id":"tt0133093","status":"watched","rating":8.5},{"url":"https://letterboxd.com/film/parasite/","status":"want"}]}'
Ошибка в одной строке не роняет остальные — в ответе результат по каждой.
DELETE /api/v1/entries/{kp_id}import requests
API = "https://ТВОЙ-САЙТ/api/v1"
KEY = "ember_xxxxxxxx"
H = {"Authorization": f"Bearer {KEY}"}
r = requests.delete(f"{API}/entries/326", headers=H)
print(r.status_code) # 204 — удалено
curl.exe -X DELETE "https://ТВОЙ-САЙТ/api/v1/entries/326" -H "Authorization: Bearer ember_xxxxxxxx"
То же самое делает PUT /entries со "status": "none".
Ничего не нашлось — попробуй другой запрос или сбрось категорию.
Это закладка-букмарклет
Перетащи кнопку «+ в ember» на панель закладок — один раз, и она будет там всегда:
- Ctrl+Shift+B — покажи панель закладок (Safari: ⌘+Shift+B)
- Перетащи кнопку «+ в ember» на эту панель
- На странице фильма жми её — ember найдёт и добавит его