Клиент LinkedIn: OAuth, профиль и посты
LinkedIn — клиент LinkedIn API в Python SDK Socialift. Он подключает участника через 3-legged OAuth и OpenID Connect (openid, profile, email), обменивает, проверяет и при наличии доступа обновляет токен, получает профиль из userinfo, а также создаёт, читает и удаляет текстовые посты и посты со ссылкой от имени участника через Posts API. Личных сообщений LinkedIn и Lead Gen Forms в клиенте нет.
from socialift import LinkedInМетодов: 10по версии socialift 1.3.0Обновлено:
Как настроить клиент LinkedIn?
Клиент создаётся так: LinkedIn(client_id=..., client_secret=...). LinkedIn.from_env() читает значения из LINKEDIN_CLIENT_ID, LINKEDIN_CLIENT_SECRET и LINKEDIN_API_VERSION. Client ID и Client Secret находятся на вкладке Auth приложения в LinkedIn Developer Portal, там же добавляется redirect URL — подробно в руководстве «Настройка приложения LinkedIn».
Доступные scope зависят от продуктов, подключённых к приложению: «Sign In with LinkedIn using OpenID Connect» — openid, profile, email; «Share on LinkedIn» — w_member_social. По умолчанию SDK запрашивает эти четыре. Для чтения постов участника нужен r_member_social — LinkedIn выдаёт это ограниченное разрешение только одобренным пользователям.
Posts API (/rest/*) требует в каждом запросе заголовок LinkedIn-Version: YYYYMM; SDK берёт его из api_version. LinkedIn поддерживает каждую ежемесячную версию минимум год, а на выведенную из эксплуатации отвечает HTTP 426 — поэтому держите api_version или LINKEDIN_API_VERSION на активной версии.
import asyncio
import os
from socialift import LinkedIn
async def main():
# 1) Явные аргументы
li = LinkedIn(
client_id=os.environ["LINKEDIN_CLIENT_ID"],
client_secret=os.environ["LINKEDIN_CLIENT_SECRET"],
api_version="202609", # активная LinkedIn-Version (YYYYMM)
)
# 2) Или из окружения: LINKEDIN_CLIENT_ID, LINKEDIN_CLIENT_SECRET,
# LINKEDIN_API_VERSION
# li = LinkedIn.from_env()
async with li:
me = await li.me(os.environ["LINKEDIN_TOKEN"]) # результат exchange_code()
print(me.id, me.full_name, li.person_urn(me.id))
asyncio.run(main())
Клиент и вход
Создание клиента, ссылка авторизации и обмен code на токен.
exchange_codefrom_envlogin_url
exchange_code
asyncОбменивает code из callback на access token в /oauth/v2/accessToken (form-urlencoded) и возвращает TokenBundle.
await li.exchange_code(code: str, *, redirect_uri: str) -> socialift.models.TokenBundle- Правило LinkedIn:
codeживёт 30 минут;redirect_uriдолжен совпадать с указанным в запросе авторизации. - Access token действует 60 дней;
refresh_tokenприходит, только если для приложения включены программные refresh token. - Если в ответе нет
access_token, будетApiError.
from_env
classmethodСоздаёт клиент из переменных окружения LINKEDIN_CLIENT_ID, LINKEDIN_CLIENT_SECRET и LINKEDIN_API_VERSION, чтобы ключи не хранились в коде.
LinkedIn.from_env(**kwargs) -> LinkedIn- Пустые значения конструктор не проверяет: отсутствие
client_idпроявится какConfigurationErrorвlogin_url. - Без
LINKEDIN_API_VERSIONиспользуется версия SDK по умолчанию — проверьте, что она ещё активна в LinkedIn.
Формирует ссылку на страницу авторизации LinkedIn (/oauth/v2/authorization); запросов не отправляет. Scope разделяются пробелом, по умолчанию: openid, profile, email, w_member_social.
li.login_url(
*,
redirect_uri: str,
state: Optional[str] = None,
scopes: Optional[list[str]] = None,
) -> str- Без
client_idбудетConfigurationError. stateзащищает от CSRF: если значение в callback не совпадает с отправленным, отклоните запрос (рекомендация LinkedIn). SDK его не хранит.- Правило LinkedIn: участник не может выбрать отдельные scope; если запросить scope, отличный от ранее выданного, все прежние access token аннулируются.
redirect_uriдолжен совпадать с одним из redirect URL на вкладке Auth приложения.
url = li.login_url(redirect_uri="https://siz.uz/cb", state=state)Управление токенами
Обновление через refresh token и проверка состояния токена.
introspect
asyncПроверяет токен через /oauth/v2/introspectToken и возвращает ответ LinkedIn словарём: active, status, expires_at, scope, auth_type и другие поля. Помогает понять, жив ли токен и когда он истечёт.
await li.introspect(token: str) -> dictstatus:active,expiredилиrevoked;scopeвозвращается только для токенов 3-legged OAuth.- Правило LinkedIn: если ключи верны, но токен принадлежит другому приложению, ответ будет 200 с
active: false— исключения нет.
info = await li.introspect(token)refresh
asyncПолучает новый access token по refresh token (grant_type=refresh_token) и возвращает TokenBundle. Нужен, чтобы не отправлять участника на повторный вход после истечения токена.
await li.refresh(refresh_token: str) -> socialift.models.TokenBundle- LinkedIn выдаёт программные refresh token только одобренным партнёрам Marketing Developer Platform; без них участник проходит авторизацию заново.
- Refresh token действует 365 дней, и обновление не продлевает этот срок; новый access token — на 60 дней.
- Если в ответе нет
access_token, будетApiError.
bundle = await li.refresh(refresh_token)Профиль
Профиль OpenID Connect и URN автора.
me
asyncВозвращает профиль участника из OpenID Connect userinfo (/v2/userinfo) в виде Profile: id (= sub), имя, фото и email. sub нужен для URN автора поста.
await li.me(token: str) -> socialift.models.Profile- Нужны scope
openidиprofile, для email —email; по документации LinkedIn полеemailнеобязательное и может отсутствовать. Profile.username— email, а если его нет —sub; вProfile.rawесть дополнительное полеlanguageиз locale.
Нужные разрешения:openidprofileemail
me = await li.me(token)person_urn
staticmethodПреобразует sub в urn:li:person:{sub} — значение author для posts и create_post. Статический метод без запросов.
LinkedIn.person_urn(sub: str) -> strcreate_postиpostsсами оборачивают так значение, не начинающееся сurn:.
Нужные разрешения:r_member_socialw_member_social
posts = await li.posts(token, author=li.person_urn(sub), count=10)post = await li.create_post(token, author=li.person_urn(sub), text="Yangiliklar!",
article_url="https://siz.uz/blog")Посты
Posts API: чтение, создание и удаление постов участника.
create_post
asyncСоздаёт от имени участника текстовый пост (при необходимости со ссылкой) через Posts API и возвращает {id, body}; id — URN поста из заголовка x-restli-id. visibility: PUBLIC или CONNECTIONS.
await li.create_post(
token: str,
*,
author: str,
text: str,
visibility: str = 'PUBLIC',
article_url: Optional[str] = None,
article_title: Optional[str] = None,
) -> dict- Нужен
w_member_social(продукт Share on LinkedIn). - Для ссылки SDK заполняет
content.articleполямиsourceиtitle(по умолчанию — сам URL). Posts API не сканирует URL, поэтому превью и описание автоматически не появятся. - Другое значение
visibilityвызываетConfigurationError. - Загрузки изображений, видео и документов в этом методе нет.
Нужные разрешения:w_member_social
post = await li.create_post(token, author=li.person_urn(sub), text="Yangiliklar!",
article_url="https://siz.uz/blog")delete_post
asyncУдаляет пост по URN (urn:li:share:... или urn:li:ugcPost:...) и возвращает {deleted: True, urn}. URN полностью кодируется в пути запроса.
await li.delete_post(token: str, post_urn: str) -> dict- Правило LinkedIn: удаление идемпотентно — для уже удалённого поста тоже вернётся 204; пакетного удаления нет.
- Нужен
w_member_social; действие необратимо.
Нужные разрешения:w_member_social
await li.delete_post(token, post_urn)posts
asyncЧитает посты автора запросом q=author к Posts API и возвращает ответ LinkedIn (elements, paging). Пагинация через start и count.
await li.posts(token: str, *, author: str, start: int = 0, count: int = 10) -> dict- Для постов участника нужен
r_member_social— ограниченное разрешение LinkedIn только для одобренных пользователей; при 403 SDK выбрасываетPermissionErrorс пояснением. - Правило LinkedIn:
countпо умолчанию 10, максимум 100; результаты отсортированы по времени последнего изменения от новых к старым. - На странице может прийти меньше
countэлементов, даже если постов больше, — следующую страницу определяйте поpaging.links.
Нужные разрешения:r_member_social
posts = await li.posts(token, author=li.person_urn(sub), count=10)Общие методы всех клиентов
У каждого HTTP-клиента (Telegram, Facebook, Instagram, LinkedIn, TikTok, Ads) есть эти низкоуровневые методы: чтобы вызвать эндпоинт, для которого в SDK ещё нет обёртки, или читать данные постранично. По завершении блока async with соединение закрывается автоматически.
request
asyncОтправляет произвольный HTTP-запрос и возвращает JSON-ответ — для эндпоинта, у которого в SDK пока нет отдельного метода. Токен и параметры подписи (access_token, для Meta — appsecret_proof через socialift.crypto.appsecret_proof) вы передаёте сами в params или headers.
await li.request(
method: str,
url: str,
*,
params: Optional[Mapping[str, Any]] = None,
data: Optional[Any] = None,
json: Optional[Any] = None,
headers: Optional[Mapping[str, str]] = None,
) -> Any- Значения
Noneвparamsне отправляются. - При лимите (
RateLimitError) и временных ошибках сервера (HTTP 429, 500, 502, 503, 504) запрос повторяется поRetryPolicy; если есть заголовокRetry-After, SDK ждёт указанное время. Обрыв сети выбрасывается какTransportError.
get
asyncКороткая форма request("GET", ...).
await li.get(url: str, **kw) -> Anypost
asyncКороткая форма request("POST", ...).
await li.post(url: str, **kw) -> Anydelete
asyncКороткая форма request("DELETE", ...).
await li.delete(url: str, **kw) -> Anypaginate
asyncПроходит по курсорной пагинации Meta (paging.next) и возвращает все элементы data одним списком.
await li.paginate(
url: str,
*,
params: Optional[Mapping[str, Any]] = None,
max_items: int = 500,
page_size: int = 100,
) -> list[dict]max_items(по умолчанию 500) — защитный предел: у крупного аккаунта могут быть десятки тысяч записей.page_size—limitодного запроса.
close
asyncЗакрывает HTTP-сессию, созданную самим SDK (переданную извне сессию не трогает). При использовании async with Client(...) вызывается автоматически.
await li.close() -> NoneЧастые вопросы
Сколько действует access token LinkedIn?
По документации LinkedIn сейчас все access token выдаются на 60 дней (expires_in). Программные refresh token доступны только одобренным партнёрам Marketing Developer Platform и действуют 365 дней; их использование не продлевает срок самого refresh token. Без refresh token участнику нужно снова пройти авторизацию.
Почему при чтении постов LinkedIn возвращается 403?
posts() читает посты автора через q=author, а для постов участника нужно разрешение r_member_social. LinkedIn относит его к ограниченным и выдаёт только одобренным пользователям. Без него SDK выбрасывает PermissionError с понятным пояснением.
Что означает ошибка 426 в LinkedIn API?
Заголовок LinkedIn-Version в запросе к /rest/* указывает на неактивную версию. LinkedIn поддерживает каждую версию минимум год, а затем отвечает 426 с кодом NONEXISTENT_VERSION. Обновите api_version или LINKEDIN_API_VERSION в SDK до активного значения YYYYMM.
Как указать автора поста в LinkedIn API?
Автор личного поста задаётся как urn:li:person:{sub}, где sub — Profile.id из me(). person_urn(sub) собирает эту строку, а create_post и posts сами оборачивают значение, если оно не начинается с urn:.
Работает ли Socialift с сообщениями LinkedIn или Lead Gen Forms?
Нет. Клиент LinkedIn охватывает только вход, токены, профиль и посты. Методов для личных сообщений и Lead Gen Forms в SDK нет.