# LinkedIn Guide for ClawkAI Agents

Use this guide to publish, upload image posts, and edit LinkedIn posts through ClawkAI backend APIs.
This is action based and works without installing custom skills on Hermes runtime.

---

## 1) Prerequisites

User must connect LinkedIn in ClawkAI dashboard first.

---

## 2) Auth and base URL

Use gateway token (exists in openclaw.json) auth for all calls.

- Base URL: `https://api.clawkai.com/v1`
- Header: `Authorization: Bearer <GATEWAY_TOKEN>`
- Content type for POST: `application/json`
- Use browser like user agent to avoid LinkedIn API blocking requests from non-browser clients.

---

## 3) Endpoints

### Publish a post

- `POST /linkedin/post`
- Body:
  - `text` (required) - post content
  - `url` (optional) - external article link to attach
  - `visibility` (optional) - `PUBLIC` (default) or `CONNECTIONS`
- Success response includes `id` and `postId` (same value). Save this for future edits.

Example: text-only post

```bash
curl -sS "https://api.clawkai.com/v1/linkedin/post" \
  -H "Authorization: Bearer $GATEWAY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Excited to share our latest ClawkAI update."
  }'
```

Example: post with link

```bash
curl -sS "https://api.clawkai.com/v1/linkedin/post" \
  -H "Authorization: Bearer $GATEWAY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "We just shipped LinkedIn posting support in ClawkAI.",
    "url": "https://clawkai.com",
    "visibility": "PUBLIC"
  }'
```

### Publish an image post

- `POST /linkedin/post/image`
- Body:
  - `text` (required) - post text
  - `imageUrl` (required) - publicly reachable `http(s)` URL of JPG/PNG/GIF image
  - `altText` (optional) - image accessibility text (max 4086 chars)
  - `visibility` (optional) - `PUBLIC` (default) or `CONNECTIONS`
- Success response includes `postId` and `imageUrn`.
- Supported image formats: `image/jpeg`, `image/png`, `image/gif`.

Example: image post

```bash
curl -sS "https://api.clawkai.com/v1/linkedin/post/image" \
  -H "Authorization: Bearer $GATEWAY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Sharing our latest launch visual.",
    "imageUrl": "https://clawkai.com/og.png",
    "altText": "Product hero image with ClawkAI dashboard preview",
    "visibility": "PUBLIC"
  }'
```

### Edit an existing post

- `POST /linkedin/post/edit`
- Body:
  - `postId` (required) - LinkedIn post URN, for example `urn:li:ugcPost:123...` or `urn:li:share:123...`
  - `text` (required) - updated post text
  - `url` (optional) - updates `contentLandingPage` on LinkedIn
- Success response includes `updated: true` and echoes `postId`.

Example: edit post text

```bash
curl -sS "https://api.clawkai.com/v1/linkedin/post/edit" \
  -H "Authorization: Bearer $GATEWAY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "postId": "urn:li:ugcPost:7351234567890123456",
    "text": "Edited copy: we shipped v2 of our ClawkAI workflow."
  }'
```

---

## 4) Python helper

```python
import os
import json
import urllib.request

BASE = "https://api.clawkai.com/v1"
TOKEN = os.environ["GATEWAY_TOKEN"]


def req(path, payload):
    headers = {
        "Authorization": f"Bearer {TOKEN}",
        "Content-Type": "application/json",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
    }
    body = json.dumps(payload).encode("utf-8")
    r = urllib.request.Request(f"{BASE}{path}", data=body, headers=headers, method="POST")
    with urllib.request.urlopen(r, timeout=60) as resp:
        return json.loads(resp.read().decode("utf-8"))


def post_linkedin(text, url=None, visibility="PUBLIC"):
    payload = {"text": text, "visibility": visibility}
    if url:
        payload["url"] = url
    return req("/linkedin/post", payload)


def post_linkedin_image(text, image_url, alt_text=None, visibility="PUBLIC"):
    payload = {"text": text, "imageUrl": image_url, "visibility": visibility}
    if alt_text:
        payload["altText"] = alt_text
    return req("/linkedin/post/image", payload)


def edit_linkedin_post(post_id, text, url=None):
    payload = {"postId": post_id, "text": text}
    if url:
        payload["url"] = url
    return req("/linkedin/post/edit", payload)
```

---

## 5) Error handling

Common API error codes:

- `linkedin_not_connected`: user must connect LinkedIn in dashboard
- `linkedin_permission_missing`: posting scope not granted
- `linkedin_token_expired`: reconnect LinkedIn
- `linkedin_token_invalid`: reconnect LinkedIn
- `linkedin_profile_missing`: reconnect LinkedIn

Typical statuses:

- `409` connection/auth issues
- `403` permission mismatch
- `400` missing/invalid post input
- `413` image too large for configured limit
- `504` timeout while downloading `imageUrl`
- `502` upstream LinkedIn API failure

---

## 6) Recommended runtime behavior

When handling a LinkedIn task:

1. Draft post copy first and keep it concise.
2. Ask user for final approval if the post is sensitive.
3. If user wants an image post, call `/linkedin/post/image` with `imageUrl` and optional `altText`.
4. Otherwise call `/linkedin/post` for text/link posts.
5. Return the resulting post id and visibility to the user.
6. If user asks to revise an already published post, call `/linkedin/post/edit` with that `postId`.
