feat(hands): improve linkedin, reddit, and twitter hands

- linkedin: API fallback strategy, queue JSON schema, 3 new settings, algorithm deep dive, crisis management
- reddit: concrete rule parsing, anti-spam/shadowban detection, engagement scoring, new settings
- twitter: structured trend analysis, queue schema, engagement criteria, algorithm awareness, new settings
This commit is contained in:
Evan Hu committed 2026-03-23 00:51:26 +09:00
1 parent de939b80d0
commit 3da827e6da
6 files changed
+3446 -89

No files matched your search

+448 -1
View File
@@ -186,6 +186,63 @@ label = "Spanish"
value = "auto"
label = "Auto-detect from network"
[[settings]]
key = "connection_request_limit"
label = "Connection Request Limit"
description = "Maximum connection requests to send per week (LinkedIn may restrict accounts exceeding 100/week)"
setting_type = "select"
default = "20"
[[settings.options]]
value = "10"
label = "10 per week (conservative)"
[[settings.options]]
value = "20"
label = "20 per week (recommended)"
[[settings.options]]
value = "50"
label = "50 per week (active)"
[[settings]]
key = "content_media_mode"
label = "Content Media Mode"
description = "Whether posts can include images or document carousels, or stay text-only"
setting_type = "select"
default = "text_only"
[[settings.options]]
value = "text_only"
label = "Text Only"
[[settings.options]]
value = "mixed"
label = "Mixed (text + occasional images/carousels)"
[[settings.options]]
value = "media_rich"
label = "Media Rich (images/carousels preferred)"
[[settings]]
key = "engagement_reply_depth"
label = "Engagement Reply Depth"
description = "How deeply to engage in comment threads on your own posts"
setting_type = "select"
default = "moderate"
[[settings.options]]
value = "minimal"
label = "Minimal (reply to direct comments only)"
[[settings.options]]
value = "moderate"
label = "Moderate (reply to comments + follow-up once)"
[[settings.options]]
value = "deep"
label = "Deep (sustain multi-turn conversations)"
# ─── Agent configuration ─────────────────────────────────────────────────────
[agent]
@@ -216,6 +273,12 @@ curl -s -H "Authorization: Bearer $LINKEDIN_ACCESS_TOKEN" \
If this fails, alert the user that the LINKEDIN_ACCESS_TOKEN is invalid or expired.
Extract your LinkedIn member URN from the response.
**Important LinkedIn API limitations to be aware of**:
- LinkedIn does NOT provide a feed-reading API for personal accounts. To "check your feed," use web_search or web_fetch on linkedin.com as a best-effort alternative. Feed-based engagement features may be limited.
- OAuth tokens expire (typically 60 days for access tokens). Always handle 401 errors gracefully.
- The analytics/statistics endpoints require Organization-level access. Personal accounts can only track post-level metrics by re-fetching their own posts.
- There is no API endpoint for reading connection requests or DMs. These features are web-only.
Recover state:
1. memory_recall `linkedin_hand_state` — load previous posting history and performance data
2. Read **User Configuration** for content_style, post_frequency, content_topics, etc.
@@ -284,8 +347,58 @@ Only queue A and B grade content. Rewrite C-grade or discard entirely.
## Phase 3 — Posting & Queue Management
### Queue file schema (`linkedin_queue.json`)
```json
{
"version": 1,
"updated_at": "2025-03-15T10:30:00Z",
"posts": [
{
"id": "q-20250315-001",
"created_at": "2025-03-15T10:30:00Z",
"scheduled_for": "2025-03-17T08:30:00Z",
"status": "pending_review",
"grade": "A",
"moderation": "SAFE",
"pillar": "industry_insights",
"format": "story",
"content": {
"commentary": "Full post text here...",
"media_type": "none",
"media_path": null,
"first_comment": "Link or extra context for first comment"
},
"hashtags": ["#Leadership", "#Engineering"],
"review_note": "Strong hook, timely topic"
}
]
}
```
Status values: `pending_review`, `approved`, `rejected`, `posted`, `failed`.
### Posting history schema (`linkedin_posted.json`)
```json
{
"version": 1,
"posts": [
{
"queue_id": "q-20250315-001",
"linkedin_post_urn": "urn:li:share:7654321",
"posted_at": "2025-03-17T08:30:00Z",
"metrics_snapshot": {
"impressions": 0,
"reactions": 0,
"comments": 0,
"reposts": 0,
"last_checked": "2025-03-17T08:30:00Z"
}
}
]
}
```
If `approval_mode` is ENABLED:
1. Write generated posts to `linkedin_queue.json`
1. Write generated posts to `linkedin_queue.json` using the schema above
2. Write a human-readable `linkedin_queue_preview.md` for review
3. event_publish "linkedin_queue_updated" with queue size
4. Do NOT post — wait for user approval
@@ -301,6 +414,20 @@ If `approval_mode` is DISABLED:
Note: LinkedIn is migrating from ugcPosts to the newer Posts API. Use /rest/posts endpoint. The author urn format: urn:li:person:{your-linkedin-id}
2. Log each posted content to `linkedin_posted.json`
### Media post handling (when `content_media_mode` is not `text_only`)
- **Images**: Use the 3-step images API flow (register upload -> PUT binary -> create post with image URN). Store the local image path in `media_path` in the queue entry. If image upload fails, fall back to posting as text-only with a note in `review_note`.
- **Document carousels**: Use the documents API flow. Generate carousel slides as a PDF. If PDF generation or upload fails, extract key points and post as a numbered-list text post instead.
- Always keep the text version as fallback — never silently skip a post because media upload failed.
### API failure fallback strategy
When a LinkedIn API call fails during posting:
1. **HTTP 401 (token expired)**: Alert the user immediately via event_publish "linkedin_token_expired". Queue all remaining posts. Do NOT retry — token refresh requires user action.
2. **HTTP 403 (scope missing)**: Log which scope is missing (e.g., w_member_social). Alert user. Queue post for retry after scope is granted.
3. **HTTP 429 (rate limited)**: Read `X-RateLimit-Reset` header. Reschedule the post for after the reset window. Log the delay in the queue entry.
4. **HTTP 5xx (server error)**: Retry up to 3 times with exponential backoff (2s, 4s, 8s). If all retries fail, move post status to `failed` and schedule retry for next session.
5. **Network error / timeout**: Same as 5xx handling.
6. After any failure, always persist queue state to `linkedin_queue.json` before exiting, so no content is lost.
---
## Phase 4 — Engagement
@@ -311,6 +438,22 @@ If `auto_engage` is enabled:
3. Like posts from people in your professional network
4. NEVER leave generic comments — always add genuine insight
Reply depth follows the `engagement_reply_depth` setting:
- **minimal**: Reply only to direct top-level comments on your posts
- **moderate**: Reply to top-level comments and one follow-up per thread
- **deep**: Sustain multi-turn conversations; ask follow-up questions to keep threads alive (this boosts dwell time signals)
### Connection request personalization
When sending connection requests (up to `connection_request_limit` per week):
1. **Research first**: Read the person's headline, recent posts, and shared connections before writing the note
2. **Personalize the note** (under 300 characters). Templates by context:
- Shared content interest: "Hi [Name], your post on [topic] resonated — especially [specific point]. Would love to connect and exchange ideas."
- Mutual connection: "Hi [Name], [Mutual] and I worked on [context]. They mentioned your work on [topic] — would be great to connect."
- Event/group: "Hi [Name], enjoyed your comment in [Group/Event] about [topic]. Let's connect!"
3. **Never pitch** in the connection request. Save business conversations for after the connection is accepted.
4. **Track acceptance rate** in memory. If acceptance rate drops below 40%, reduce volume and improve note quality.
5. **Respect the weekly limit** strictly — LinkedIn may restrict accounts that send excessive requests.
---
## Phase 5 — Performance Tracking
@@ -404,6 +547,7 @@ default_active = false
[i18n.zh]
name = "LinkedIn Hand"
description = "自主 LinkedIn 管理——个人资料优化、内容创作、人脉拓展和职业互动"
<<<<<<< HEAD
category = "通信"
[i18n.zh.settings.content_style]
@@ -632,3 +776,306 @@ description = "콘텐츠의 주요 대상 독자"
[i18n.ko.settings.language]
label = "언어"
description = "게시물 및 소통에 사용하는 언어"
||||||| parent of e65ad25 (feat(hands): improve linkedin, reddit, and twitter hands)
=======
category = "通信"
[i18n.zh.settings.content_style]
label = "内容风格"
description = "LinkedIn 帖子的语气和风格"
[i18n.zh.settings.post_frequency]
label = "发布频率"
description = "创建和发布内容的频率"
[i18n.zh.settings.content_topics]
label = "内容主题"
description = "要创作内容的主题(逗号分隔,例如 AI、领导力、创业)"
[i18n.zh.settings.auto_engage]
label = "自动互动"
description = "自动对人脉网络中的相关帖子点赞和评论"
[i18n.zh.settings.approval_mode]
label = "审批模式"
description = "将帖子加入队列等待审核,而非直接发布"
[i18n.zh.settings.hashtag_count]
label = "话题标签数量"
description = "每篇帖子包含的话题标签数量"
[i18n.zh.settings.target_audience]
label = "目标受众"
description = "内容的主要目标受众"
[i18n.zh.settings.language]
label = "语言"
description = "帖子和互动使用的语言"
[i18n.zh.settings.connection_request_limit]
label = "连接请求上限"
description = "每周发送连接请求的最大数量(超过 100/周可能导致账号被限制)"
[i18n.zh.settings.content_media_mode]
label = "内容媒体模式"
description = "帖子是否包含图片或文档轮播,还是仅使用纯文本"
[i18n.zh.settings.engagement_reply_depth]
label = "互动回复深度"
description = "在自己帖子的评论区中参与讨论的深度"
# ─── Japanese (日本語) ────────────────────────────────────────────────────
[i18n.ja]
name = "LinkedIn Hand"
description = "自律型LinkedInマネージャー——プロフィール最適化、コンテンツ作成、ネットワーキング、プロフェッショナルエンゲージメント"
category = "コミュニケーション"
[i18n.ja.settings.content_style]
label = "コンテンツスタイル"
description = "LinkedIn投稿の語調とスタイル"
[i18n.ja.settings.post_frequency]
label = "投稿頻度"
description = "コンテンツの作成・投稿の頻度"
[i18n.ja.settings.content_topics]
label = "コンテンツトピック"
description = "作成するコンテンツのトピック(カンマ区切り、例: AI、リーダーシップ、スタートアップ)"
[i18n.ja.settings.auto_engage]
label = "自動エンゲージメント"
description = "ネットワーク内の関連投稿に自動でいいねやコメントをする"
[i18n.ja.settings.approval_mode]
label = "承認モード"
description = "投稿を直接公開せず、レビュー用キューに追加する"
[i18n.ja.settings.hashtag_count]
label = "ハッシュタグ数"
description = "各投稿に含めるハッシュタグの数"
[i18n.ja.settings.target_audience]
label = "ターゲットオーディエンス"
description = "コンテンツの主なターゲット層"
[i18n.ja.settings.language]
label = "言語"
description = "投稿とエンゲージメントに使用する言語"
[i18n.ja.settings.connection_request_limit]
label = "つながり申請上限"
description = "週あたりのつながり申請送信上限数(100件/週を超えるとアカウントが制限される可能性があります)"
[i18n.ja.settings.content_media_mode]
label = "コンテンツメディアモード"
description = "投稿に画像やドキュメントカルーセルを含めるか、テキストのみにするか"
[i18n.ja.settings.engagement_reply_depth]
label = "エンゲージメント返信深度"
description = "自分の投稿のコメントスレッドにどの程度深く参加するか"
# ─── Spanish (Español) ────────────────────────────────────────────────────
[i18n.es]
name = "Hand de LinkedIn"
description = "Gestor autónomo de LinkedIn — optimización de perfil, creación de contenido, networking y engagement profesional"
category = "Comunicación"
[i18n.es.settings.content_style]
label = "Estilo de contenido"
description = "Voz y tono para las publicaciones de LinkedIn"
[i18n.es.settings.post_frequency]
label = "Frecuencia de publicación"
description = "Con qué frecuencia crear y publicar contenido"
[i18n.es.settings.content_topics]
label = "Temas de contenido"
description = "Temas sobre los que crear contenido (separados por comas, ej. IA, liderazgo, startups)"
[i18n.es.settings.auto_engage]
label = "Engagement automático"
description = "Dar like y comentar automáticamente en publicaciones relevantes de tu red"
[i18n.es.settings.approval_mode]
label = "Modo de aprobación"
description = "Poner publicaciones en cola para revisión en lugar de publicarlas directamente"
[i18n.es.settings.hashtag_count]
label = "Cantidad de hashtags"
description = "Número de hashtags a incluir por publicación"
[i18n.es.settings.target_audience]
label = "Audiencia objetivo"
description = "Audiencia principal para tu contenido"
[i18n.es.settings.language]
label = "Idioma"
description = "Idioma para publicaciones e interacciones"
[i18n.es.settings.connection_request_limit]
label = "Limite de solicitudes de conexion"
description = "Numero maximo de solicitudes de conexion por semana (superar 100/semana puede restringir la cuenta)"
[i18n.es.settings.content_media_mode]
label = "Modo de contenido multimedia"
description = "Si las publicaciones incluyen imagenes o carruseles de documentos, o solo texto"
[i18n.es.settings.engagement_reply_depth]
label = "Profundidad de respuesta"
description = "Nivel de participacion en los hilos de comentarios de tus publicaciones"
# ─── French (Français) ────────────────────────────────────────────────────
[i18n.fr]
name = "Hand LinkedIn"
description = "Gestionnaire LinkedIn autonome — optimisation de profil, création de contenu, réseautage et engagement professionnel"
category = "Communication"
[i18n.fr.settings.content_style]
label = "Style de contenu"
description = "Ton et style pour les publications LinkedIn"
[i18n.fr.settings.post_frequency]
label = "Fréquence de publication"
description = "Fréquence de création et de publication de contenu"
[i18n.fr.settings.content_topics]
label = "Sujets de contenu"
description = "Sujets sur lesquels créer du contenu (séparés par des virgules, ex. IA, leadership, startups)"
[i18n.fr.settings.auto_engage]
label = "Engagement automatique"
description = "Aimer et commenter automatiquement les publications pertinentes de votre réseau"
[i18n.fr.settings.approval_mode]
label = "Mode d'approbation"
description = "Mettre les publications en file d'attente pour révision au lieu de les publier directement"
[i18n.fr.settings.hashtag_count]
label = "Nombre de hashtags"
description = "Nombre de hashtags à inclure par publication"
[i18n.fr.settings.target_audience]
label = "Public cible"
description = "Public principal pour votre contenu"
[i18n.fr.settings.language]
label = "Langue"
description = "Langue pour les publications et les interactions"
[i18n.fr.settings.connection_request_limit]
label = "Limite de demandes de connexion"
description = "Nombre maximum de demandes de connexion par semaine (depasser 100/semaine peut entrainer des restrictions)"
[i18n.fr.settings.content_media_mode]
label = "Mode media du contenu"
description = "Inclure des images ou carrousels dans les publications, ou rester en texte uniquement"
[i18n.fr.settings.engagement_reply_depth]
label = "Profondeur de reponse"
description = "Niveau d'implication dans les fils de commentaires de vos publications"
# ─── German (Deutsch) ────────────────────────────────────────────────────
[i18n.de]
name = "LinkedIn-Hand"
description = "Autonomer LinkedIn-Manager — Profiloptimierung, Content-Erstellung, Networking und professionelles Engagement"
category = "Kommunikation"
[i18n.de.settings.content_style]
label = "Inhaltsstil"
description = "Ton und Stil für LinkedIn-Beiträge"
[i18n.de.settings.post_frequency]
label = "Veröffentlichungshäufigkeit"
description = "Wie oft Inhalte erstellt und veröffentlicht werden"
[i18n.de.settings.content_topics]
label = "Inhaltsthemen"
description = "Themen für die Content-Erstellung (kommagetrennt, z.B. KI, Führung, Startups)"
[i18n.de.settings.auto_engage]
label = "Automatisches Engagement"
description = "Relevante Beiträge im Netzwerk automatisch liken und kommentieren"
[i18n.de.settings.approval_mode]
label = "Genehmigungsmodus"
description = "Beiträge zur Überprüfung in die Warteschlange stellen, anstatt sie direkt zu veröffentlichen"
[i18n.de.settings.hashtag_count]
label = "Anzahl Hashtags"
description = "Anzahl der Hashtags pro Beitrag"
[i18n.de.settings.target_audience]
label = "Zielgruppe"
description = "Primäre Zielgruppe für Ihre Inhalte"
[i18n.de.settings.language]
label = "Sprache"
description = "Sprache für Beiträge und Interaktionen"
[i18n.de.settings.connection_request_limit]
label = "Kontaktanfragen-Limit"
description = "Maximale Anzahl an Kontaktanfragen pro Woche (über 100/Woche kann zu Einschränkungen führen)"
[i18n.de.settings.content_media_mode]
label = "Inhalts-Medien-Modus"
description = "Ob Beiträge Bilder oder Dokumentenkarussells enthalten oder nur Text verwenden"
[i18n.de.settings.engagement_reply_depth]
label = "Antworttiefe"
description = "Wie intensiv in Kommentarthreads der eigenen Beiträge mitdiskutiert wird"
# ─── Korean (한국어) ────────────────────────────────────────────────────
[i18n.ko]
name = "LinkedIn Hand"
description = "자율 LinkedIn 관리 — 프로필 최적화, 콘텐츠 제작, 네트워킹 및 전문적 소통"
category = "커뮤니케이션"
[i18n.ko.settings.content_style]
label = "콘텐츠 스타일"
description = "LinkedIn 게시물의 어조와 스타일"
[i18n.ko.settings.post_frequency]
label = "게시 빈도"
description = "콘텐츠를 작성하고 게시하는 주기"
[i18n.ko.settings.content_topics]
label = "콘텐츠 주제"
description = "콘텐츠를 작성할 주제 (쉼표로 구분, 예: AI, 리더십, 스타트업)"
[i18n.ko.settings.auto_engage]
label = "자동 소통"
description = "네트워크 내 관련 게시물에 자동으로 좋아요 및 댓글"
[i18n.ko.settings.approval_mode]
label = "승인 모드"
description = "게시물을 직접 게시하지 않고 대기열에 추가하여 검토"
[i18n.ko.settings.hashtag_count]
label = "해시태그 수"
description = "게시물당 포함할 해시태그 수"
[i18n.ko.settings.target_audience]
label = "대상 독자"
description = "콘텐츠의 주요 대상 독자"
[i18n.ko.settings.language]
label = "언어"
description = "게시물 및 소통에 사용하는 언어"
[i18n.ko.settings.connection_request_limit]
label = "연결 요청 한도"
description = "주당 최대 연결 요청 수 (100건/주 초과 시 계정이 제한될 수 있음)"
[i18n.ko.settings.content_media_mode]
label = "콘텐츠 미디어 모드"
description = "게시물에 이미지나 문서 캐러셀을 포함할지, 텍스트만 사용할지 설정"
[i18n.ko.settings.engagement_reply_depth]
label = "소통 답글 깊이"
description = "자신의 게시물 댓글 스레드에 얼마나 깊이 참여할지 설정"
>>>>>>> e65ad25 (feat(hands): improve linkedin, reddit, and twitter hands)
+828 -5
View File
@@ -83,11 +83,29 @@ curl -s -X POST "https://api.linkedin.com/rest/socialActions/URN/likes" \
### The LinkedIn Algorithm (2024-2025)
Key factors that affect reach:
1. **Dwell time**: How long people spend reading your post
2. **Early engagement**: Comments in the first hour boost distribution
3. **Meaningful comments**: Long comments signal quality content
4. **No external links**: Posts with links get 40-50% less reach
5. **Personal stories**: Narrative content outperforms promotional content
1. **Dwell time**: How long people spend reading your post. LinkedIn tracks both "read dwell" (time spent on post text) and "click dwell" (time spent after clicking "see more"). Longer posts that hold attention get amplified. Ideal: 800-1300 characters that reward reading to the end.
2. **Early engagement**: Comments in the first 60-90 minutes are weighted heavily. The algorithm decides distribution tiers within 2 hours of posting.
3. **Meaningful comments**: Long comments (3+ sentences) signal quality far more than likes. One thoughtful comment is worth ~10 likes in the algorithm. Reply-to-reply threads (nested comments) further boost the post.
4. **No external links**: Posts with links get 40-50% less reach. The algorithm deprioritizes anything that drives users off-platform.
5. **Personal stories**: Narrative content outperforms promotional content. The algorithm favors "knowledge and advice" posts from individuals over brand content.
**Engagement signal weighting** (approximate relative impact on distribution):
| Signal | Relative Weight | Why |
|--------|----------------|-----|
| Comment (3+ sentences) | 10x | Strongest indicator of quality content |
| Repost with commentary | 8x | Shows content worth sharing and adding to |
| Save/bookmark | 6x | High-intent signal — user wants to revisit |
| Reply in comment thread | 5x | Sustained conversation signals value |
| Share (plain repost) | 4x | Distribution signal but lower intent |
| Reaction (any emoji) | 1x | Baseline engagement, lowest weight |
| Click "see more" | 0.5x | Curiosity signal, but no follow-through guarantee |
**Algorithm penalty signals**:
- Editing a post within 10 minutes of publishing can reset distribution
- Deleting and reposting gets flagged and suppressed
- Posting more than once per 18 hours splits your audience
- Engagement pods (coordinated likes/comments) are detected and penalized
- Hashtag stuffing (>5) triggers spam signals
### Content Pillars
@@ -218,6 +236,7 @@ Before posting any content, classify it:
- Credit sources and tag collaborators
- Disclose affiliations when discussing products or services
- Respect intellectual property and copyright
<<<<<<< HEAD
---
@@ -968,3 +987,807 @@ DMs are open. Or drop a comment and I'll reach out.
#OpenToWork #EngineeringManager #Hiring #Leadership
```
||||||| parent of e65ad25 (feat(hands): improve linkedin, reddit, and twitter hands)
=======
### Crisis Management for Negative Engagement
When a post receives significant negative attention (hostile comments, public disagreements, misinterpretation):
**Severity levels and response**:
| Level | Indicators | Action |
|-------|-----------|--------|
| **Low** | 1-2 disagreeing comments, professional tone | Respond thoughtfully; treat as healthy discussion |
| **Medium** | Multiple negative comments, some personal attacks, post being quote-shared critically | Pause auto-engagement; draft a measured clarification comment; queue for user review |
| **High** | Viral negative attention, accusations of misinformation, brand/employer reputation risk | Alert user immediately via event_publish "linkedin_crisis_alert"; do NOT auto-respond; prepare a response draft for human approval |
**Response playbook**:
1. **Never delete a post** that has active engagement -- it signals guilt and people screenshot first
2. **Never argue in comment threads** -- one measured response per critic, then disengage
3. **Acknowledge valid criticism** gracefully: "That's a fair point -- I should have been clearer about [X]. Here's what I meant: ..."
4. **For factual errors** in your post: Add a correction comment pinned at the top: "Update: [correction]. Thanks to @Name for pointing this out."
5. **For personal attacks**: Do not respond. Hide the comment (LinkedIn allows this) and move on. If persistent, report to LinkedIn.
6. **For misinterpretation at scale**: Write a follow-up post (not an edit) that clarifies the original point without being defensive
**After a crisis**: Log the incident in memory, note what triggered it, and update content moderation rules to prevent recurrence.
---
## Advanced API Patterns
### Image Post Creation (Media Upload Flow)
Posting an image requires a 3-step flow: register upload, upload binary, then create post.
**Step 1 -- Register the upload**:
```bash
curl -s -X POST "https://api.linkedin.com/rest/images?action=initializeUpload" \
-H "Authorization: Bearer $LINKEDIN_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "LinkedIn-Version: 202405" \
-d '{
"initializeUploadRequest": {
"owner": "urn:li:person:MEMBER_ID"
}
}'
```
Response contains `uploadUrl` and `image` URN (e.g., `urn:li:image:C4E...`).
**Step 2 -- Upload the binary**:
```bash
curl -s -X PUT "$UPLOAD_URL" \
-H "Authorization: Bearer $LINKEDIN_ACCESS_TOKEN" \
-H "Content-Type: image/png" \
--data-binary "@/path/to/image.png"
```
**Step 3 -- Create post with image**:
```bash
curl -s -X POST "https://api.linkedin.com/rest/posts" \
-H "Authorization: Bearer $LINKEDIN_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "LinkedIn-Version: 202405" \
-d '{
"author": "urn:li:person:MEMBER_ID",
"lifecycleState": "PUBLISHED",
"commentary": "Check out our Q3 results!",
"visibility": "PUBLIC",
"distribution": {"feedDistribution": "MAIN_FEED"},
"content": {
"media": {
"id": "urn:li:image:IMAGE_URN",
"title": "Q3 Performance Summary"
}
}
}'
```
### Document Post Creation (PDF/Carousel)
LinkedIn "document posts" (carousels) follow the same register-upload-post pattern but use the documents API.
**Register document upload**:
```bash
curl -s -X POST "https://api.linkedin.com/rest/documents?action=initializeUpload" \
-H "Authorization: Bearer $LINKEDIN_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "LinkedIn-Version: 202405" \
-d '{
"initializeUploadRequest": {
"owner": "urn:li:person:MEMBER_ID"
}
}'
```
**Upload the PDF and create post** (same pattern as image -- PUT binary, then POST with `content.media.id` set to the document URN).
### Article Publishing via API
**Create an article post** (link article hosted externally):
```bash
curl -s -X POST "https://api.linkedin.com/rest/posts" \
-H "Authorization: Bearer $LINKEDIN_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "LinkedIn-Version: 202405" \
-d '{
"author": "urn:li:person:MEMBER_ID",
"lifecycleState": "PUBLISHED",
"commentary": "I wrote about why most engineering teams get incident response wrong.\n\nKey insight: the 5-minute rule changes everything.",
"visibility": "PUBLIC",
"distribution": {"feedDistribution": "MAIN_FEED"},
"content": {
"article": {
"source": "https://yourblog.com/incident-response",
"title": "The 5-Minute Rule for Incident Response",
"description": "A practical framework for engineering teams"
}
}
}'
```
> **Note**: Article-link posts get reduced reach vs native text posts. Prefer putting links in the first comment.
### Analytics Endpoints
**Get post statistics (organic)**:
```bash
curl -s -H "Authorization: Bearer $LINKEDIN_ACCESS_TOKEN" \
-H "LinkedIn-Version: 202405" \
"https://api.linkedin.com/rest/organizationalEntityShareStatistics?q=organizationalEntity&organizationalEntity=urn:li:organization:ORG_ID&timeIntervals.timeGranularityType=DAY&timeIntervals.timeRange.start=1704067200000&timeIntervals.timeRange.end=1706745600000"
```
**Get share statistics for a specific post**:
```bash
curl -s -H "Authorization: Bearer $LINKEDIN_ACCESS_TOKEN" \
-H "LinkedIn-Version: 202405" \
"https://api.linkedin.com/rest/organizationalEntityShareStatistics?q=organizationalEntity&organizationalEntity=urn:li:organization:ORG_ID&shares=urn:li:share:SHARE_ID"
```
Response fields:
| Field | Description |
|-------|-------------|
| `totalShareStatistics.impressionCount` | Total times the post appeared in feeds |
| `totalShareStatistics.uniqueImpressionsCount` | Unique viewers |
| `totalShareStatistics.clickCount` | Total clicks (content + read more) |
| `totalShareStatistics.likeCount` | Total likes/reactions |
| `totalShareStatistics.commentCount` | Total comments |
| `totalShareStatistics.shareCount` | Total reposts |
| `totalShareStatistics.engagement` | Engagement rate (decimal) |
**Get follower statistics** (organization pages):
```bash
curl -s -H "Authorization: Bearer $LINKEDIN_ACCESS_TOKEN" \
-H "LinkedIn-Version: 202405" \
"https://api.linkedin.com/rest/organizationalEntityFollowerStatistics?q=organizationalEntity&organizationalEntity=urn:li:organization:ORG_ID"
```
### Webhook / Notification Patterns
LinkedIn does not offer real-time webhooks for most events. Use polling instead:
```
Polling strategy:
- Post engagement: Poll every 15 minutes for first 4 hours after posting
- Mentions/comments: Poll every 5 minutes during engagement_hours
- Follower counts: Poll once per day
- Analytics: Poll once per day (data lags 24-48 hours)
```
### Error Handling and Rate Limit Retry
```
Rate limit response headers:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1706745600
HTTP 429 response:
{"status": 429, "message": "Resource level throttle limit..."}
```
**Retry strategy**:
```
1. On HTTP 429: Read X-RateLimit-Reset header
2. Calculate wait_seconds = reset_timestamp - current_timestamp
3. Sleep for wait_seconds + 1 (buffer)
4. Retry the request (max 3 retries)
5. On 3 consecutive 429s: back off for 15 minutes
On HTTP 5xx (server error):
1. Retry with exponential backoff: 1s, 2s, 4s
2. Max 3 retries
3. Log failure and queue for later retry
On HTTP 401 (expired token):
1. Trigger OAuth 2.0 refresh flow
2. Update stored token
3. Retry original request once
```
---
## Content Calendar Template
### Monthly Content Planning Framework
Organize content around weekly themes that rotate through your content pillars.
```
MONTH: [Month Year]
THEME ROTATION:
Week 1: [Pillar 1 -- e.g., Engineering Leadership]
Week 2: [Pillar 2 -- e.g., Industry Trends]
Week 3: [Pillar 3 -- e.g., Career Growth]
Week 4: [Pillar 1 deep dive OR seasonal/timely topic]
```
### Weekly Content Schedule
```
WEEK OF [DATE] — Theme: [Weekly Theme]
Monday:
- 8:30 AM: [Personal story] tied to weekly theme
Format: Hook + narrative + lesson + question
Goal: High engagement to start the week
Tuesday:
- 9:00 AM: [Step-by-step guide] or [How-to]
Format: Numbered list with tactical advice
Goal: Saves and shares (authority building)
Wednesday:
- 8:30 AM: [Data/insight post] with original analysis
Format: Stat + context + your take + question
Goal: Credibility and thought leadership
Thursday:
- 9:00 AM: [Contrarian take] or [Industry opinion]
Format: Bold statement + reasoning + invitation to debate
Goal: Comments and discussion (algorithm boost)
Friday:
- 8:00 AM: [Engagement post] — poll, question, or lightweight personal content
Format: Short, conversational, easy to respond to
Goal: Community building before weekend
```
### Content Mix Ratios
| Category | % of Posts | Examples |
|----------|-----------|----------|
| Educational / Value | 40% | How-tos, frameworks, lessons learned |
| Personal / Storytelling | 25% | Career stories, failures, reflections |
| Engagement / Discussion | 20% | Questions, polls, contrarian takes |
| Promotional / Company | 10% | Product launches, hiring, milestones |
| Curated / Commentary | 5% | Industry news with your analysis |
**Rule**: Never let promotional content exceed 15%. LinkedIn penalizes overtly sales-y accounts.
### Engagement Windows and Response Strategy
```
Post published at 8:30 AM:
Minutes 0-15: Reply to EVERY comment immediately (signals activity to algorithm)
Minutes 15-60: Reply within 5 minutes of each new comment
Hours 1-4: Reply within 30 minutes
Hours 4-24: Reply within 2 hours (during business hours)
Day 2+: Reply within 24 hours
First-comment strategy:
- Post your own comment within 2 minutes of publishing
- Use it for: link to resource, additional context, question to spark discussion
- This comment acts as engagement seed
```
---
## Analytics & Optimization
### Key Metrics to Track
| Metric | Formula | Good Benchmark | Great Benchmark |
|--------|---------|----------------|-----------------|
| Engagement rate | (reactions + comments + reposts) / impressions | > 2% | > 5% |
| Comment rate | comments / impressions | > 0.3% | > 1% |
| Follower growth rate | net new followers / total followers per week | > 0.5% | > 2% |
| Profile views | weekly profile views trend | Consistent growth | 2x after viral post |
| SSI (Social Selling Index) | LinkedIn's built-in score (0-100) | > 50 | > 70 |
| Content saves | saves / impressions | > 0.5% | > 2% |
| Click-through rate | clicks / impressions | > 1% | > 3% |
### Engagement Rate Calculation
```
engagement_rate = (reactions + comments + reposts) / impressions * 100
Example:
120 reactions + 35 comments + 8 reposts = 163 engagements
163 / 5,200 impressions = 3.13% engagement rate
Per-post tracking:
| Post Date | Topic | Format | Impressions | Eng Rate | Comments |
|-----------|-------|--------|-------------|----------|----------|
| Mon 03/03 | Leadership | Story | 5,200 | 3.13% | 35 |
| Tue 03/04 | AI Tools | How-to | 3,800 | 4.21% | 22 |
| Wed 03/05 | Hiring | Data | 2,100 | 2.85% | 12 |
```
### A/B Testing Strategies
Test one variable at a time across pairs of similar posts:
| Variable | Option A | Option B | Track |
|----------|----------|----------|-------|
| Hook style | Question hook | Bold statement hook | Click-through rate |
| Post length | Short (< 800 chars) | Long (1200+ chars) | Dwell time, engagement |
| Posting time | 8:00 AM | 9:30 AM | Impressions after 4 hours |
| CTA type | Question CTA | "Agree? Repost" CTA | Comment rate vs repost rate |
| Hashtag count | 3 hashtags | 0 hashtags | Reach beyond network |
| Format | Plain text | Text + image | Engagement rate |
**How to run a test**:
1. Pick one variable to test (e.g., posting time)
2. Keep everything else constant (same pillar, similar format, similar length)
3. Run for 2 weeks (minimum 4 posts per variant)
4. Compare average metrics -- ignore outliers
5. Adopt the winner and move to next variable
**A/B test tracking template** (store in `linkedin_ab_tests.json`):
```json
{
"test_id": "test-hook-style-001",
"variable": "hook_style",
"hypothesis": "Bold statement hooks generate higher click-through than question hooks",
"start_date": "2025-03-10",
"end_date": "2025-03-24",
"status": "running",
"variant_a": {
"description": "Question hook",
"post_ids": ["q-20250310-001", "q-20250312-001", "q-20250314-001"],
"avg_impressions": 3200,
"avg_engagement_rate": 2.8,
"avg_comment_rate": 0.4
},
"variant_b": {
"description": "Bold statement hook",
"post_ids": ["q-20250311-001", "q-20250313-001", "q-20250315-001"],
"avg_impressions": 4100,
"avg_engagement_rate": 3.5,
"avg_comment_rate": 0.6
},
"conclusion": null
}
```
**Statistical rigor**: With LinkedIn's natural variance, require at least 4 posts per variant and a >20% difference in the primary metric before declaring a winner. If the difference is <20%, the test is inconclusive -- run for another week or accept that the variable does not materially affect performance.
### Identifying Top-Performing Content Patterns
After 30+ posts, analyze your data to find patterns:
```
Sort all posts by engagement rate (descending):
1. Look at your top 5 posts — what do they share?
- Same content pillar?
- Same format (story, how-to, contrarian)?
- Same hook style?
- Similar length range?
- Same posting day/time?
2. Look at your bottom 5 posts — what went wrong?
- External links in body?
- Promotional tone?
- Published on Friday/weekend?
- Weak hook?
3. Create your "hit formula":
Best combo: [Pillar] + [Format] + [Hook style] + [Day/Time]
Example: "Engineering Leadership + Personal Story + Confession Hook + Tuesday 8:30 AM"
```
---
## Audience Growth Strategies
### Comment-First Strategy
The fastest way to grow on LinkedIn is strategic commenting on high-visibility posts.
**How it works**:
1. Identify 15-20 active creators in your niche (10K+ followers)
2. Turn on notifications for their posts
3. Be among the first 5 comments on their new posts
4. Write substantive comments (3-5 sentences) that add genuine value
**Comment templates for growth**:
```
Adding a data point:
"This resonates. At [Company/Role], we saw [specific metric] when we
implemented [related approach]. The key difference was [insight].
Curious if others have seen similar results?"
Respectful counterpoint:
"Interesting perspective. I'd push back slightly on [point] — in my
experience with [context], the opposite was true because [reason].
That said, I think [original point] absolutely holds for [use case]."
Extending the idea:
"Building on this — one thing I'd add is [new angle]. I wrote about
this recently and the biggest takeaway was [specific insight].
[Question that invites further discussion]?"
```
**Target**: 5-10 thoughtful comments per day during peak hours (8-10 AM).
### Collaborative Content Patterns
**Tagging strategy**:
- Tag 1-3 people who would genuinely find the content relevant
- Always explain WHY you're tagging them (not drive-by tags)
- Tag people you've already engaged with (they're more likely to respond)
```
Example post with strategic tags:
"I've been thinking about how engineering teams handle on-call rotations.
After talking to 20+ eng managers, here are the 3 models that actually work:
1. Follow-the-sun (best for distributed teams)
2. Volunteer-first rotation (best for small teams)
3. Tiered escalation (best for complex systems)
@Name1 — your team's approach to #2 was eye-opening.
@Name2 — curious if your distributed team uses #1 or something else?
What model does your team use? Reply with your team size."
```
**Co-creation patterns**:
- Interview a peer and post key insights (tag them, they reshare)
- "X people I learned from this year" posts (mass tagging, high reshare rate)
- Collaborative lists: "Drop your best [resource] in the comments, I'll compile and share"
### LinkedIn Newsletter Strategy
Newsletters convert profile visitors into subscribers with direct inbox delivery.
**Newsletter setup checklist**:
```
1. Name: Clear, specific, benefit-driven
Good: "The Engineering Leader's Playbook"
Bad: "My Thoughts on Things"
2. Cadence: Weekly or biweekly (consistency > frequency)
3. Format:
- 800-1500 words (longer than posts, shorter than blog articles)
- One core idea per issue
- Actionable takeaways or frameworks
- End with a question to drive comments
4. Promotion:
- Announce each issue with a teaser post (don't just auto-share)
- Reference newsletter content in regular posts
- Cross-promote with other newsletter authors
```
**Newsletter content structure**:
```
Issue #[N]: [Compelling Title]
[Hook paragraph -- why this matters NOW]
[Section 1: The Problem / Context]
- 2-3 paragraphs with specific examples
[Section 2: The Framework / Solution]
- Numbered steps or clear model
- Real-world application examples
[Section 3: How to Apply This]
- Actionable next steps the reader can take today
[Closing: Question + CTA]
"What's your experience with [topic]? Reply in the comments."
"If you found this useful, share it with your team."
```
### LinkedIn Live and Events
**LinkedIn Live** broadcasts get 7x more reactions and 24x more comments than regular video posts.
**Live session framework**:
```
Pre-event (1 week before):
- Create LinkedIn Event and post announcement
- Send invites to relevant connections
- Post 2-3 teaser posts building anticipation
During event:
- Start 2 minutes early for tech check
- Open with clear agenda (30 seconds)
- Acknowledge live commenters by name
- Keep sessions 20-40 minutes
Post-event:
- Post key takeaways within 2 hours
- Reply to all comments on the event post
- Repurpose recording into 3-5 short clips for future posts
```
**Event types that work**:
| Type | Duration | Best For | Frequency |
|------|----------|----------|-----------|
| AMA (Ask Me Anything) | 30 min | Engagement, authority | Monthly |
| Industry deep dive | 20 min | Thought leadership | Biweekly |
| Interview / fireside chat | 40 min | Network growth | Monthly |
| Quick tip / hot take | 10 min | Visibility | Weekly |
---
## Worked Examples
### Example 1: Thought Leadership Campaign
**Scenario**: VP of Engineering building authority in "engineering culture" niche.
**Content pillars**:
```
Pillar 1: Engineering Management (40%)
Pillar 2: Scaling Teams (30%)
Pillar 3: Career Advice (20%)
Pillar 4: Personal Lessons (10%)
```
**Week 1 posting schedule with sample posts**:
**Monday 8:30 AM -- Personal Story (Pillar 1)**:
```
I promoted my worst interviewer to Head of Recruiting.
Sounds crazy. Here's what happened.
She kept rejecting candidates everyone else loved.
Her "pass rate" was 15%. Team average was 60%.
But after 12 months, something became clear:
Her hires had:
→ 94% retention rate (team avg: 71%)
→ 2.3x faster time to first meaningful contribution
→ Zero PIPs in their first year
She wasn't a bad interviewer.
She was the only one actually doing it right.
The lesson?
Measure what matters. Pass rates reward speed.
Retention rates reward judgment.
What's one metric your team optimizes for
that might be the wrong one?
#EngineeringLeadership #Hiring #TechManagement
```
**Tuesday 9:00 AM -- How-To Guide (Pillar 2)**:
```
How to run a team retrospective that people actually enjoy
(not the soul-crushing ones everyone dreads):
Step 1: Kill the "what went well / what didn't" format
→ Use "I wish... I wonder... I'm proud of..." instead
Step 2: Timebox ruthlessly
→ 45 minutes max. If it takes longer, your team is too big for one retro.
Step 3: One action item per person, max
→ A retro with 20 action items produces zero change.
→ One item per person = accountability.
Step 4: Start with appreciation
→ First 5 minutes: each person thanks someone else on the team.
→ This changes the entire energy of the room.
Step 5: Rotate the facilitator
→ The manager should NOT always run retros.
→ It changes what people feel safe saying.
I've used this format with teams of 5 to teams of 50.
What's your retro format? Drop it below --
I'm always looking for new approaches.
#Agile #EngineeringCulture #TeamManagement
```
**Wednesday 8:30 AM -- Data + Insight (Pillar 2)**:
```
We tracked every engineering team meeting for 6 months.
The data was uncomfortable.
→ Average engineer: 11.2 hours/week in meetings
→ Senior engineers: 16.4 hours/week
→ Time spent in meetings that could've been async: 62%
We cut 40% of recurring meetings.
Result after 3 months:
→ Sprint velocity: +23%
→ Engineer satisfaction: +31% (internal survey)
→ "Deep work" blocks per week: 2.1 → 4.7
The surprising part?
Nobody missed the deleted meetings.
Not one person asked to bring them back.
If you haven't audited your meeting load recently,
you're probably burning 30-40% of your team's capacity.
What % of your meetings could be an async update?
#Engineering #Productivity #Leadership
```
**Thursday 9:00 AM -- Contrarian Take (Pillar 3)**:
```
Unpopular opinion: "Culture fit" interviews should be illegal.
Here's why:
Culture fit = "do I want to get a beer with this person?"
That's not hiring. That's friend-making.
What actually matters:
→ Values alignment (do they care about the same outcomes?)
→ Working style compatibility (async vs sync, docs vs meetings)
→ Growth trajectory (will they push the team forward?)
None of those require "fitting in."
The best hire I ever made was someone who challenged
every assumption we had. They didn't "fit" our culture.
They made it better.
Replace "culture fit" with "culture add."
Agree or disagree? I'd love to hear your take.
#Hiring #Diversity #EngineeringCulture #Leadership
```
**Friday 8:00 AM -- Engagement Post (Pillar 4)**:
```
Fill in the blank:
"The best career advice I ever received was ___________."
I'll go first:
"Stop optimizing for your next promotion.
Start optimizing for your next learning curve."
Changed how I made every career decision since.
Your turn.
#CareerAdvice #ProfessionalGrowth
```
### Example 2: Company Page Management
**Scenario**: B2B SaaS company (Series B, 80 employees) managing their LinkedIn company page.
**Posting cadence**:
```
Company page: 4-5 posts per week
Employee advocacy: 2-3 employees reshare/post per week
Executive accounts: CEO + CTO post 2-3x/week each
```
**Weekly company page schedule**:
```
Monday: Industry insight or thought leadership (educational)
Tuesday: Product tip or customer use case (value-driven)
Wednesday: Team/culture spotlight (employer branding)
Thursday: Data or trend analysis (authority)
Friday: Milestone, hiring, or community post (engagement)
```
**Sample company page posts**:
**Tuesday -- Customer Use Case**:
```
"We used to spend 3 hours every Monday pulling reports manually."
That's what @CustomerName's ops team told us last quarter.
After switching to [Product] automated workflows:
→ Report generation: 3 hours → 12 minutes
→ Data accuracy: 89% → 99.7%
→ Team freed up: 12 hours/week for strategic work
The best part? They set it up in a single afternoon.
Read the full story: [link in first comment]
#DataAutomation #Operations #CustomerSuccess
```
**Wednesday -- Team Culture Spotlight**:
```
This is Sarah. She joined us as intern #3 two years ago.
Last week she deployed our new ML pipeline to production.
By herself. On a Tuesday. No drama.
What happened in between:
→ Mentored by 4 different senior engineers
→ Shipped 47 PRs in her first year
→ Gave her first conference talk at 23
→ Now leads a team of 3
We don't hire for credentials.
We hire for curiosity and grit.
Sarah had both.
We're hiring 5 more engineers just like her.
Link in the comments.
#Hiring #Engineering #StartupCulture #WomenInTech
```
**Employee advocacy tracking**:
```
| Employee | Role | Posts/Week | Avg Reach | Topics |
|----------|------|-----------|-----------|--------|
| CEO | Executive | 3 | 8,500 | Vision, industry, leadership |
| CTO | Executive | 2 | 5,200 | Technical, architecture, hiring |
| VP Eng | Leader | 2 | 3,100 | Engineering culture, management |
| DevRel | IC | 3 | 4,800 | Tutorials, product, community |
```
**Analytics tracking cadence**:
```
Daily: Check post-level engagement (reactions, comments, shares)
Weekly: Follower growth, top-performing post, engagement rate trend
Monthly: Content audit — which pillars/formats performed best
Adjust next month's content mix based on data
Quarterly: Competitor benchmarking, SSI review, strategy refresh
```
### Example 3: Job Seeker Profile Optimization Campaign
**Scenario**: Senior developer transitioning to engineering management role.
**4-week content plan**:
```
Week 1: Establish expertise
- Post about a technical decision you led and its business impact
- Share a "lessons from my first year managing" story
- Comment on 10 engineering leadership posts
Week 2: Demonstrate thought leadership
- Publish a how-to post: "How I transitioned from IC to manager"
- Share data or a framework you've developed
- Start engaging with hiring managers' content in target companies
Week 3: Build social proof
- Post about a mentoring success story (tag the mentee with permission)
- Share a "things I wish I knew" post targeting new managers
- Request 3-5 recommendations from colleagues and reports
Week 4: Signal availability
- Post about what you're looking for (without desperation)
- Engage heavily in target company employees' content
- Send personalized connection requests to hiring managers
```
**Sample "open to opportunities" post**:
```
After 8 years of writing code and 2 years of leading teams,
I'm looking for my next engineering management challenge.
What I bring to the table:
→ Scaled a team from 4 to 22 engineers
→ Reduced deployment failures by 73% through better process
→ Mentored 6 engineers into senior roles
→ Built hiring pipelines that maintained 85%+ offer acceptance
What I'm looking for:
→ Series A-C company building something meaningful
→ Team of 8-20 engineers who care about craft
→ Leadership that values engineering culture, not just velocity
If your team is growing and you value
managers who still understand the code --
I'd love to chat.
DMs are open. Or drop a comment and I'll reach out.
#OpenToWork #EngineeringManager #Hiring #Leadership
```
>>>>>>> e65ad25 (feat(hands): improve linkedin, reddit, and twitter hands)