Хранилище контента
Индексируйте, ищите и анализируйте своё хранилище OnlyFans — находите любое медиа за одно нажатие, видьте, что реально продаётся, и получайте подобранные по когортам рекомендации контента.
Хранилище контента — это поисковый, размеченный ИИ индекс каждого медиа-элемента в вашем хранилище OnlyFans, плюс аналитика поверх него. Как только ваше хранилище проиндексировано, вы можете найти любой клип или сет по названию или тегу за одно нажатие, вытащить целую папку как бандл и — что гораздо ценнее — читать, какой контент реально приносит выручку, что лежит без дела, где ваш спрос обгоняет предложение и какие невиданные части отправить данной когорте следующими.
Каждый endpoint ниже находится по адресу https://app.tease.link/api/admin/vault и аутентифицируется
Bearer-al_live_* ключом. Все чтения требуют scope read;
триггер сканирования и авто-тег требуют write. Каждый ответ — это JSON, привязанный к
владельцу, выпустившему ключ — вы всегда видите только своё собственное хранилище.
Индекс хранилища строится и обновляется целиком на стороне TEASE. Вы никогда не загружаете нам медиа и вам никогда не нужно поддерживать индекс вручную — вы запускаете сканирование, затем читаете из него.
Как всё устроено
Индексировать
Запустите сканирование, чтобы построить или обновить поисковый индекс вашего хранилища.
Найти
Ищите по названию или тегу, откройте папку или вытащите целый дроп как бандл.
Анализировать
Топ-продавцы, пробелы контента и спящий контент — что отправить, отложить или снять.
Рекомендовать
Подобранные по когортам рекомендации невиданного контента, ранжированные по соответствию.
Сканировать и индексировать хранилище
Индексирование — это фоновая задача. Вы запускаете её, затем опрашиваете статус, пока он не устаканится. Сканирование идемпотентно — повторный запуск обновляет индекс, а не дублирует его.
Запустить сканирование
curl -X POST "https://app.tease.link/api/admin/vault/scan" \
-H "Authorization: Bearer $TEASE_API_KEY"Триггер сканирования не принимает тела или query-параметров.
{ "started": true, "already_running": false }Если сканирование уже в процессе, вызов является no-op, и вы получаете { "started": false, "already_running": true }. Если ваш аккаунт OnlyFans ещё не подключён, вызов возвращает
400 с сообщением, говорящим вам сначала завершить подключение.
Опросить статус
curl "https://app.tease.link/api/admin/vault/scan/status" \
-H "Authorization: Bearer $TEASE_API_KEY"{
"folders": 84,
"items": 5120,
"ai_tagged": 4870,
"last_indexed_ts": 1751280000,
"running": false,
"last_error": ""
}| Поле | Тип | Описание |
|---|---|---|
folders | integer | Число проиндексированных папок/альбомов. |
items | integer | Число проиндексированных медиа-элементов. |
ai_tagged | integer | Сколько элементов уже несут ИИ-теги. |
last_indexed_ts | integer | Unix-секунды последнего успешного индекса. 0, если никогда не индексировалось. |
running | boolean | true, пока сканирование в процессе — опрашивайте, пока не перевернётся в false. |
last_error | string | Пусто при успехе; короткое сообщение, если последнее сканирование сбойнуло. |
Индексирование — клиентская функция, а не API, который вы собираете
Как индекс строится из вашего аккаунта OnlyFans и как выводятся теги, обрабатывается целиком на стороне сервера. От вас не ожидается перечислять медиа или генерировать теги самостоятельно — запустите сканирование и читайте результаты.
Поиск по хранилищу
Мгновенный поиск по названию и тегу через индекс. Результаты возвращаются сгруппированными по папке, с любыми свободными (без папки) элементами, вынесенными отдельно, так что пикер контента панели может отрендерить дерево мгновенно.
curl "https://app.tease.link/api/admin/vault/search?q=lingerie&scope=all&limit=200" \
-H "Authorization: Bearer $TEASE_API_KEY"Query-параметры
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
q | string | нет | Поисковый текст, сопоставляемый с названиями и тегами. Пусто возвращает всё (вплоть до limit). |
scope | string | нет | all (по умолчанию), sorted (только элементы, разложенные в папку) или unsorted (свободные элементы). |
limit | integer | нет | Макс. число возвращаемых элементов, 1–1000. По умолчанию 200. |
Ответ
{
"query": "lingerie",
"scope": "all",
"folders": [
{
"folder_id": "f_2291",
"name": "Red lingerie set",
"items": [
{
"media_id": "m_88120",
"media_type": "photo",
"folder_id": "f_2291",
"folder_name": "Red lingerie set",
"sorted": true,
"title": "red set — teaser",
"has_cover": true
}
]
}
],
"loose": [],
"counts": { "folders": 1, "items": 1 }
}| Поле | Тип | Описание |
|---|---|---|
query | string | Эхо поискового текста. |
scope | string | Эхо фильтра scope. |
folders | array | Найденные элементы, сгруппированные по их папке. См. форму элемента ниже. |
loose | array | Найденные элементы, которые не находятся ни в одной папке. |
counts.folders | integer | Число папок в результате. |
counts.items | integer | Всего найденных элементов. |
Каждый элемент несёт: media_id, media_type (photo, video, gif, …), folder_id,
folder_name, sorted (разложен ли он), title и has_cover (доступна ли миниатюра —
см. обложки).
Поиск возвращает media_id-ы, никогда не байты медиа. Используйте media_id-ы, чтобы скомпоновать PPV в
панели, или получите миниатюру из endpoint-а обложки.
Открыть папку
Вытащите каждый элемент в одной папке — действие «выбрать весь пак». Ответ включает
плоский массив media_ids, готовый к вставке прямо в композер сообщений.
curl "https://app.tease.link/api/admin/vault/folder/f_2291" \
-H "Authorization: Bearer $TEASE_API_KEY"{
"folder_id": "f_2291",
"name": "Red lingerie set",
"media_ids": ["m_88120", "m_88121", "m_88122"],
"items": [
{ "media_id": "m_88120", "media_type": "photo", "title": "red set — teaser", "has_cover": true }
]
}| Поле | Тип | Описание |
|---|---|---|
folder_id | string | Запрошенный id папки. |
name | string | Имя папки, или пусто, если неизвестно. |
media_ids | array | Каждый id медиа в папке, по порядку. |
items | array | Полная запись элемента для каждого id (та же форма, что у поиска). |
Вытащить дроп
Папку также можно прочитать как дроп — её элементы сгруппированы по роли (video, photoset,
bts, other) с заранее упорядоченным bundle_media_ids для отправки в одно касание «отправить весь дроп».
Это естественная единица, которую продаёт чаттер.
curl "https://app.tease.link/api/admin/vault/drop/f_2291" \
-H "Authorization: Bearer $TEASE_API_KEY"{
"folder_id": "f_2291",
"name": "Red lingerie set",
"cover_media_id": "m_88120",
"roles": {
"video": { "count": 1, "media_ids": ["m_88130"] },
"photoset": { "count": 8, "media_ids": ["m_88120", "m_88121"] },
"bts": { "count": 2, "media_ids": ["m_88140", "m_88141"] },
"other": { "count": 0, "media_ids": [] }
},
"bundle_media_ids": ["m_88130", "m_88120", "m_88121", "m_88140", "m_88141"],
"counts": { "total": 11, "video": 1, "photoset": 8, "bts": 2, "other": 0 },
"is_drop": true
}| Поле | Тип | Описание |
|---|---|---|
cover_media_id | string | Предлагаемая обложка для дропа. |
roles | object | Элементы, разбитые на video, photoset, bts, other, каждый со счётчиком и id. |
bundle_media_ids | array | Весь дроп в порядке отправки — вставьте прямо в композер. |
counts | object | total плюс счётчик по каждой роли. |
is_drop | boolean | Непрозрачный флаг, который TEASE устанавливает, когда папка квалифицируется как дроп. |
Обложки
Получите изображение-миниатюру для любого id медиа. Endpoint возвращает изображение-миниатюру
(image/jpeg), так что направьте <img> прямо на него — обложка есть только у элементов, где has_cover равно true.
curl "https://app.tease.link/api/admin/vault/cover/m_88120" \
-H "Authorization: Bearer $TEASE_API_KEY" \
-o cover.jpgВозвращает 404, когда у id медиа нет обложки. Endpoint всегда отдаёт миниатюру только для
id медиа в вашем собственном хранилище.
Авто-тег бэклога
auto-tag запускает ИИ-тегирование по вашим нетегированным элементам, чтобы они стали поисковыми и начали
поступать в аналитику эффективности, пробелов и рекомендаций. ИИ-тегирование выполняется на стороне сервера.
Это явное действие — оно никогда не прочёсывает всё ваше хранилище само по себе — и оно
ограничено по стоимости и идемпотентно:
уже тегированные элементы пропускаются и никогда не оплачиваются повторно, а limit зажимается до жёсткого
серверного потолка, а не отклоняется.
curl -X POST "https://app.tease.link/api/admin/vault/auto-tag" \
-H "Authorization: Bearer $TEASE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "all_untagged": true, "limit": 100 }'const res = await fetch('https://app.tease.link/api/admin/vault/auto-tag', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.TEASE_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ all_untagged: true, limit: 100 }),
});
const result = await res.json();res = requests.post(
"https://app.tease.link/api/admin/vault/auto-tag",
headers={"Authorization": f"Bearer {os.environ['TEASE_API_KEY']}"},
json={"all_untagged": True, "limit": 100},
)
result = res.json()Параметры тела
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
media_ids | string[] | нет | Явные id для тегирования. Опустите, чтобы использовать бэклог вместо этого. |
all_untagged | boolean | нет | Когда true, тегировать нетегированный бэклог вплоть до limit. По умолчанию false. |
limit | integer | нет | Макс. число элементов для тегирования в этом вызове. Зажимается до жёсткого серверного потолка, так что завышенное значение зажимается, а не отклоняется. |
Ответ
{
"tagged": [{ "media_id": "m_88120", "tags": ["lingerie", "solo", "teaser"] }],
"tagged_count": 1,
"skipped": [{ "media_id": "m_88121", "reason": "already_tagged" }],
"skipped_count": 1,
"errors": [],
"capped": false,
"requested": 2
}| Поле | Тип | Описание |
|---|---|---|
tagged | array | Элементы, получившие новые теги, каждый со своим media_id и применёнными tags. |
tagged_count | integer | Счётчик заново тегированных элементов. |
skipped | array | Пропущенные элементы, каждый с reason (например, уже тегирован, нечего тегировать). |
skipped_count | integer | Счётчик пропущенных элементов. |
errors | array | Сбои по элементам. Плохой элемент деградирует до ошибки здесь — он никогда не выдаёт 500 на весь вызов. |
capped | boolean | true, если requested превысил потолок и запуск был урезан. |
requested | integer | Сколько элементов вызов попытался обработать до зажима. |
Тегирование безопасно к деградации по элементам: единственный элемент, который не может быть тегирован, возвращается в errors
или skipped с причиной; остальная часть пакета всё равно успешна. Повторный запуск всегда безопасен —
тегированные элементы пропускаются, так что вам никогда не выставляют счёт дважды за то же медиа.
Что продаётся — эффективность
performance ранжирует ваше хранилище по выручке: какие отдельные медиа и какие теги реально приводят к
покупкам, каждый с unlock rate (как часто фанат, получивший его, его купил). Это
чтение за «что работает» в панели.
curl "https://app.tease.link/api/admin/vault/performance?limit=20" \
-H "Authorization: Bearer $TEASE_API_KEY"Query-параметры
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
from_ts | integer | нет | Начало окна, Unix-секунды. По умолчанию 30 дней назад. |
to_ts | integer | нет | Конец окна, Unix-секунды. По умолчанию сейчас. |
limit | integer | нет | Макс. число строк на список (медиа и теги), 1–200. По умолчанию 20. |
Ответ
{
"range": { "from_ts": 1748688000, "to_ts": 1751280000 },
"media": [
{
"media_id": "m_88130",
"title": "red set — full video",
"media_type": "video",
"folder_name": "Red lingerie set",
"has_cover": true,
"tags": ["lingerie", "solo"],
"sent": 247,
"opened": 181,
"purchased": 89,
"revenue_cents": 142800,
"unlock_rate": 0.34
}
],
"tags": [
{ "tag": "lingerie", "sent": 988, "opened": 731, "purchased": 301, "revenue_cents": 461500, "unlock_rate": 0.29 }
],
"ppv_sends": 1320
}| Поле | Тип | Описание |
|---|---|---|
range | object | Разрешённое окно from_ts/to_ts. |
media | array | Топ медиа по выручке. См. поля строки ниже. |
tags | array | Топ тегов по выручке, с теми же sent/opened/purchased/revenue_cents/unlock_rate. |
ppv_sends | integer | Всего PPV-отправок, подсчитанных в окне. |
Каждая строка медиа несёт media_id, title, media_type, folder_name, has_cover, tags
и числа эффективности:
| Поле | Тип | Описание |
|---|---|---|
sent | integer | Сколько раз это медиа было отправлено в окне. |
opened | integer | Сколько раз оно было открыто/просмотрено. |
purchased | integer | Сколько раз оно было разблокировано/куплено. |
revenue_cents | integer | Чистая выручка, которую оно принесло, в центах. |
unlock_rate | number | null | Доля получателей, которые его разблокировали. null, когда слишком мало отправок, чтобы быть значимым. |
unlock_rate сообщается как исход, уже подавленный, когда выборка слишком тонка, чтобы
доверять — вам не нужно вычислять его или решать, когда он надёжен.
Что снять следующим — пробелы
gaps отвечает на «что мне снять?», выявляя теги, где спрос обгоняет предложение — темы,
которые хорошо продаются, но где у вас мало элементов для отправки — наряду с тем, сколько вашего хранилища ещё
неразложено или нетегировано.
curl "https://app.tease.link/api/admin/vault/gaps?limit=20" \
-H "Authorization: Bearer $TEASE_API_KEY"Query-параметры
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
from_ts | integer | нет | Начало окна, Unix-секунды. По умолчанию 30 дней назад. |
to_ts | integer | нет | Конец окна, Unix-секунды. По умолчанию сейчас. |
limit | integer | нет | Макс. число строк shoot_more, 1–200. По умолчанию 20. |
Ответ
{
"range": { "from_ts": 1748688000, "to_ts": 1751280000 },
"shoot_more": [
{
"tag": "shower",
"revenue_cents": 88200,
"purchased": 61,
"sent": 140,
"rank": 1
}
],
"total_items": 5120,
"unsorted": 380,
"untagged": 250
}| Поле | Тип | Описание |
|---|---|---|
range | object | Разрешённое окно. |
shoot_more | array | Теги, ранжированные как недопоставленные для их спроса — ваш список съёмки. См. строки ниже. |
total_items | integer | Всего проиндексированных элементов. |
unsorted | integer | Элементы, ещё не разложенные в папку. |
untagged | integer | Элементы без ИИ-тегов пока — кандидаты на авто-тег. |
Каждая строка shoot_more сообщает revenue_cents, purchased, sent тега и его rank —
его позицию в списке съёмки, где 1 — самый недопоставленный для своего спроса. Список
приходит уже упорядоченным; вам не нужно выводить ранжирование.
unsorted и untagged — это сигнал бэклога: элементы там не могут всплыть в поиске или
аналитике, пока не будут разложены или тегированы. Запустите авто-тег, чтобы очистить
счётчик нетегированных.
Что остыло — спящее
dormant возвращает элементы хранилища, у которых была нулевая активность отправок в окне — контент, который
просто лежит там и мог бы быть переотправлен. Это разность множеств между вашим хранилищем и тем, что
реально продавалось или отправлялось недавно; ничто в чистом итоге вашего аккаунта OnlyFans не затрагивается.
curl "https://app.tease.link/api/admin/vault/dormant?window_days=30" \
-H "Authorization: Bearer $TEASE_API_KEY"Query-параметры
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
window_days | integer | нет | Окно обратного отсчёта, 1–3650 дней. По умолчанию 30. |
Ответ
{
"items": [
{
"media_id": "m_70011",
"title": "beach throwback",
"tags": ["outdoor", "bikini"],
"cover": true,
"last_sent": 1741000000
}
],
"count": 1,
"window_days": 30
}| Поле | Тип | Описание |
|---|---|---|
items | array | Спящее медиа. У каждого есть media_id, title, tags, cover и last_sent. |
count | integer | Число спящих элементов. |
window_days | integer | Эхо использованного окна. |
last_sent | integer | null | Когда элемент был отправлен в последний раз (Unix-секунды), или null, если никогда. |
Когортные рекомендации
recommend — это самое ценное чтение хранилища: для заданной когорты фанатов оно возвращает невиданный
контент — элементы, которые этой когорте не отправлялись — ранжированные по тому, насколько хорошо они подходят. Направьте его
на контент (рекомендовать для людей, купивших данную часть) или опишите когорту теми же
фильтрами аудитории, которые использует композер рассылок.
curl "https://app.tease.link/api/admin/vault/recommend?source=tiktok&rfm_tag=whale&limit=20" \
-H "Authorization: Bearer $TEASE_API_KEY"Query-параметры
Укажите либо content_id (рекомендовать для когорты, купившей этот контент), либо один
или более фильтров аудитории. Все параметры необязательны и комбинируются как AND-фильтр.
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
content_id | integer | нет | Построить когорту из покупателей этого контента. |
source | string | нет | Фильтровать когорту по источнику привлечения (например, tiktok). |
country | string | нет | Фильтровать по стране (ISO-код). |
rfm_tag | string | нет | Фильтровать по RFM/lifecycle-тегу (например, whale, dormant). |
spend_min | integer | нет | Минимальные пожизненные траты, в центах. |
spend_max | integer | нет | Максимальные пожизненные траты, в центах. |
inactive_days | integer | нет | Только фанаты, неактивные как минимум столько дней. |
bought_ppv | boolean | нет | Ограничить фанатами, которые покупали (или не покупали) PPV. |
new_vs_returning | string | нет | new или returning. |
is_trial | boolean | нет | Ограничить пробными (или не-пробными) фанатами. |
limit | integer | нет | Макс. число рекомендаций, 1–200. По умолчанию 20. |
Ответ
{
"items": [
{
"media_id": "m_88130",
"title": "red set — full video",
"tags": ["lingerie", "solo"],
"cover": true,
"unlock_rate": 0.4,
"fit_reason": "Recommended for this cohort"
}
],
"cohort_summary": {
"filters": { "source": "tiktok", "rfm_tag": "whale" },
"source": "tiktok",
"fan_count": 312
},
"thin": false
}| Поле | Тип | Описание |
|---|---|---|
items | array | Рекомендованный невиданный контент, лучшее соответствие первым. См. поля строки ниже. |
cohort_summary | object | Разрешённая когорта: применённые filters, доминирующий source и fan_count. Когда построена из content_id, она также несёт from_content_id и buyer_count. |
thin | boolean | true, когда у когорты мало покупательского сигнала, так что ранжирование менее уверенное. Относитесь к порядку как к более слабой подсказке. |
Каждая строка рекомендации несёт media_id, title, tags, cover, unlock_rate и
короткую, человекочитаемую fit_reason, которую вы можете показать рядом с подсказкой.
Рекомендация — это исход, а не рецепт
TEASE выбирает и ранжирует невиданный контент за вас — какие сигналы питают ранжирование и как
они взвешиваются, является внутренним. Вы даёте ему когорту; он даёт вам упорядоченный список того, что эта
когорта не видела и с наибольшей вероятностью купит. Флаг thin говорит вам, когда доверять меньше.