Skip to content

Instantly share code, notes, and snippets.

@sebsto
Last active April 22, 2026 07:20
Show Gist options
  • Select an option

  • Save sebsto/e16707e33139fcd3f53e77b6187aad89 to your computer and use it in GitHub Desktop.

Select an option

Save sebsto/e16707e33139fcd3f53e77b6187aad89 to your computer and use it in GitHub Desktop.
Social Media Agent

Social Media Posting — AI Agent Reference

This document contains all credentials and instructions needed for an AI agent to post on your social media accounts. Read this file before posting. Each platform section is self-contained with credentials, posting instructions, and curl samples.


Quick Reference

Platform Auth method Token expiry Max length Status
Mastodon Bearer token Never 500 chars ✅ Ready
LinkedIn OAuth2 Bearer Varies (~2 months) ~3000 chars ✅ Ready
Bluesky App password + session Session: 2h, password: never 300 chars ✅ Ready
X OAuth 1.0a Never 280 chars ⚠️ Pay per use credits are mandatory since April 2026

Mastodon

Credentials

How to get these: Go to your Mastodon instance → Preferences → Development → New Application. Grant write:statuses and write:media scopes. Copy the access token. See: https://docs.joinmastodon.org/client/token/

  • Instance : https://<YOUR_INSTANCE> (e.g. https://mastodon.social)
  • Handle : @<YOUR_HANDLE>@<YOUR_INSTANCE>
  • Access Token : <MASTODON_ACCESS_TOKEN>
  • Token expiry : never

How to post

curl -s -X POST https://<YOUR_INSTANCE>/api/v1/statuses \
  -H "Authorization: Bearer <MASTODON_ACCESS_TOKEN>" \
  -d "status=MESSAGE_HERE" \
  -d "visibility=public"

Options

  • visibility : public, unlisted, private, direct

Posting with an image

Doc: https://docs.joinmastodon.org/methods/media/

  1. Upload the image → get a media_id
  2. Attach the media_id to the status
# Step 1: Upload image
curl -s -X POST https://<YOUR_INSTANCE>/api/v2/media \
  -H "Authorization: Bearer <MASTODON_ACCESS_TOKEN>" \
  -F "file=@/path/to/image.png" \
  -F "description=Alt text for accessibility"

# Step 2: Post with media_id from step 1
curl -s -X POST https://<YOUR_INSTANCE>/api/v1/statuses \
  -H "Authorization: Bearer <MASTODON_ACCESS_TOKEN>" \
  -d "status=MESSAGE_HERE" \
  -d "media_ids[]=MEDIA_ID_HERE" \
  -d "visibility=public"

Notes

  • Max 500 characters per post
  • Supports hashtags natively (#hashtag)
  • No token refresh needed
  • Images: max 4 per post, formats jpg/png/gif, max 16MB

LinkedIn

Credentials

How to get these:

  1. You must first create a Company Page on LinkedIn (even a dummy one) — it's required to create a developer app. Go to https://www.linkedin.com/company/setup/new/ and fill in the minimum fields (name, URL — can be any URL you own or a placeholder).
  2. Create an app at https://www.linkedin.com/developers/apps → associate it with your Company Page.
  3. On the app's Products tab, request access to Share on LinkedIn (w_member_social) and Sign In with LinkedIn using OpenID Connect (openid, profile).
  4. Go to the Auth tab to copy your Client ID and Client Secret.
  5. Follow the OAuth flow below to get an access token.

See: https://learn.microsoft.com/en-us/linkedin/shared/authentication/authorization-code-flow

  • Client ID : <LINKEDIN_CLIENT_ID>
  • Client Secret : <LINKEDIN_CLIENT_SECRET>
  • Access Token : <LINKEDIN_ACCESS_TOKEN>
  • Token expiry : ~2 months from creation (check your token's actual expiry)
  • Member URN : urn:li:person:<YOUR_MEMBER_ID>
  • Redirect URI : https://httpbin.org/get (or any URI you control)

How to post

curl -s -X POST "https://api.linkedin.com/rest/posts" \
  -H "Authorization: Bearer <LINKEDIN_ACCESS_TOKEN>" \
  -H "Content-Type: application/json" \
  -H "LinkedIn-Version: 202504" \
  -H "X-Restli-Protocol-Version: 2.0.0" \
  -d '{
    "author": "urn:li:person:<YOUR_MEMBER_ID>",
    "commentary": "MESSAGE_HERE",
    "visibility": "PUBLIC",
    "distribution": {
      "feedDistribution": "MAIN_FEED",
      "targetEntities": [],
      "thirdPartyDistributionChannels": []
    },
    "lifecycleState": "PUBLISHED",
    "isReshareDisabledByAuthor": false
  }'

Posting with an image

Doc: https://learn.microsoft.com/en-us/linkedin/marketing/community-management/shares/images-api

  1. Register the upload → get an uploadUrl and image URN
  2. Upload the binary image to the uploadUrl
  3. Create the post referencing the image URN
# Step 1: Register upload
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: 202504" \
  -H "X-Restli-Protocol-Version: 2.0.0" \
  -d '{"initializeUploadRequest": {"owner": "urn:li:person:<YOUR_MEMBER_ID>"}}'

# Step 2: Upload binary (use uploadUrl from step 1)
curl -s -X PUT "UPLOAD_URL_HERE" \
  -H "Authorization: Bearer <LINKEDIN_ACCESS_TOKEN>" \
  --upload-file /path/to/image.png

# Step 3: Post with image (use image URN from step 1)
curl -s -X POST "https://api.linkedin.com/rest/posts" \
  -H "Authorization: Bearer <LINKEDIN_ACCESS_TOKEN>" \
  -H "Content-Type: application/json" \
  -H "LinkedIn-Version: 202504" \
  -H "X-Restli-Protocol-Version: 2.0.0" \
  -d '{
    "author": "urn:li:person:<YOUR_MEMBER_ID>",
    "commentary": "MESSAGE_HERE",
    "visibility": "PUBLIC",
    "distribution": {
      "feedDistribution": "MAIN_FEED",
      "targetEntities": [],
      "thirdPartyDistributionChannels": []
    },
    "content": {
      "media": {
        "id": "IMAGE_URN_HERE",
        "title": "Image title"
      }
    },
    "lifecycleState": "PUBLISHED",
    "isReshareDisabledByAuthor": false
  }'

Options

  • visibility : PUBLIC, CONNECTIONS
  • feedDistribution : MAIN_FEED, NONE

Posting with a URL (no image)

When the message contains a URL but no image, add a content.article block to force LinkedIn to render a link preview card. Without this, the API may not crawl the URL for OG metadata.

curl -s -X POST "https://api.linkedin.com/rest/posts" \
  -H "Authorization: Bearer <LINKEDIN_ACCESS_TOKEN>" \
  -H "Content-Type: application/json" \
  -H "LinkedIn-Version: 202504" \
  -H "X-Restli-Protocol-Version: 2.0.0" \
  -d '{
    "author": "urn:li:person:<YOUR_MEMBER_ID>",
    "commentary": "MESSAGE_HERE",
    "visibility": "PUBLIC",
    "distribution": {
      "feedDistribution": "MAIN_FEED",
      "targetEntities": [],
      "thirdPartyDistributionChannels": []
    },
    "content": {
      "article": {
        "source": "URL_HERE",
        "title": "TITLE_HERE",
        "description": "DESCRIPTION_HERE"
      }
    },
    "lifecycleState": "PUBLISHED",
    "isReshareDisabledByAuthor": false
  }'

Note: when posting with an image, use the content.media block instead (see above). Do not combine article and media.

Posting with a video URL (e.g. YouTube)

LinkedIn's content.article block alone does not reliably render a video thumbnail/vignette for YouTube links. To force a proper link card with the video thumbnail, you must:

  1. Download the YouTube thumbnail image (e.g. https://img.youtube.com/vi/VIDEO_ID/maxresdefault.jpg)
  2. Upload it to LinkedIn as an image (register upload → upload binary)
  3. Reference the image URN in the content.article.thumbnail field
# Step 1: Download YouTube thumbnail
curl -s -o /tmp/yt_thumb.jpg "https://img.youtube.com/vi/VIDEO_ID/maxresdefault.jpg"

# Step 2: Register image upload
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: 202504" \
  -H "X-Restli-Protocol-Version: 2.0.0" \
  -d '{"initializeUploadRequest": {"owner": "urn:li:person:<YOUR_MEMBER_ID>"}}'

# Step 3: Upload the thumbnail (use uploadUrl from step 2)
curl -s -X PUT "UPLOAD_URL_HERE" \
  -H "Authorization: Bearer <LINKEDIN_ACCESS_TOKEN>" \
  --upload-file /tmp/yt_thumb.jpg

# Step 4: Post with article + thumbnail (use image URN from step 2)
curl -s -X POST "https://api.linkedin.com/rest/posts" \
  -H "Authorization: Bearer <LINKEDIN_ACCESS_TOKEN>" \
  -H "Content-Type: application/json" \
  -H "LinkedIn-Version: 202504" \
  -H "X-Restli-Protocol-Version: 2.0.0" \
  -d '{
    "author": "urn:li:person:<YOUR_MEMBER_ID>",
    "commentary": "MESSAGE_HERE",
    "visibility": "PUBLIC",
    "distribution": {
      "feedDistribution": "MAIN_FEED",
      "targetEntities": [],
      "thirdPartyDistributionChannels": []
    },
    "content": {
      "article": {
        "source": "YOUTUBE_URL_HERE",
        "title": "VIDEO_TITLE_HERE",
        "description": "VIDEO_DESCRIPTION_HERE",
        "thumbnail": "IMAGE_URN_HERE"
      }
    },
    "lifecycleState": "PUBLISHED",
    "isReshareDisabledByAuthor": false
  }'

YouTube thumbnail URL patterns:

  • Max resolution: https://img.youtube.com/vi/VIDEO_ID/maxresdefault.jpg
  • High quality: https://img.youtube.com/vi/VIDEO_ID/hqdefault.jpg
  • Standard: https://img.youtube.com/vi/VIDEO_ID/sddefault.jpg

Try maxresdefault.jpg first; fall back to hqdefault.jpg if it returns a 404 (not all videos have max-res thumbnails).

Token refresh procedure

LinkedIn does not support refresh tokens on the free tier. When the token expires, redo the full OAuth flow:

  1. Open in browser:
https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=<LINKEDIN_CLIENT_ID>&redirect_uri=<YOUR_REDIRECT_URI>&scope=openid%20profile%20w_member_social
  1. Authorize, copy the code from the redirect URL

  2. Exchange the code:

curl -s -X POST https://www.linkedin.com/oauth/v2/accessToken \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "grant_type=authorization_code" \
  --data-urlencode "code=CODE_HERE" \
  --data-urlencode "redirect_uri=<YOUR_REDIRECT_URI>" \
  --data-urlencode "client_id=<LINKEDIN_CLIENT_ID>" \
  --data-urlencode "client_secret=<LINKEDIN_CLIENT_SECRET>" | python3 -m json.tool
  1. Update the Access Token above. The Member URN never changes.

Bluesky

Credentials

How to get these: Go to https://bsky.app → Settings → Privacy and Security → App Passwords → Add App Password. Your DID can be found by calling the com.atproto.identity.resolveHandle endpoint with your handle. See: https://atproto.com/guides/applications

  • Handle : <YOUR_HANDLE>.bsky.social
  • App Password : <BLUESKY_APP_PASSWORD>
  • DID : <YOUR_DID> (e.g. did:plc:abc123...)
  • PDS : <YOUR_PDS_URL> (e.g. https://shimeji.us-east.host.bsky.network)

How to post (two steps)

Step 1 — Create session (returns accessJwt, valid ~2 hours):

curl -s -X POST "https://bsky.social/xrpc/com.atproto.server.createSession" \
  -H "Content-Type: application/json" \
  -d '{
    "identifier": "<YOUR_HANDLE>.bsky.social",
    "password": "<BLUESKY_APP_PASSWORD>"
  }'

Step 2 — Post (use accessJwt from step 1):

NOW=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")
curl -s -X POST "<YOUR_PDS_URL>/xrpc/com.atproto.repo.createRecord" \
  -H "Authorization: Bearer ACCESS_JWT_HERE" \
  -H "Content-Type: application/json" \
  -d "{
    \"repo\": \"<YOUR_DID>\",
    \"collection\": \"app.bsky.feed.post\",
    \"record\": {
      \"\$type\": \"app.bsky.feed.post\",
      \"text\": \"MESSAGE_HERE\",
      \"createdAt\": \"$NOW\"
    }
  }"

Posting with an image

Doc: https://atproto.com/blog/create-post

  1. Upload the image blob → get a blob reference
  2. Create the post with an embed containing the blob
# Step 1: Upload image (use accessJwt from session)
curl -s -X POST "<YOUR_PDS_URL>/xrpc/com.atproto.repo.uploadBlob" \
  -H "Authorization: Bearer ACCESS_JWT_HERE" \
  -H "Content-Type: image/png" \
  --data-binary @/path/to/image.png

# Step 2: Post with image (use blob ref from step 1)
NOW=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")
curl -s -X POST "<YOUR_PDS_URL>/xrpc/com.atproto.repo.createRecord" \
  -H "Authorization: Bearer ACCESS_JWT_HERE" \
  -H "Content-Type: application/json" \
  -d "{
    \"repo\": \"<YOUR_DID>\",
    \"collection\": \"app.bsky.feed.post\",
    \"record\": {
      \"\$type\": \"app.bsky.feed.post\",
      \"text\": \"MESSAGE_HERE\",
      \"createdAt\": \"$NOW\",
      \"embed\": {
        \"\$type\": \"app.bsky.embed.images\",
        \"images\": [{
          \"alt\": \"Alt text for accessibility\",
          \"image\": BLOB_REF_FROM_STEP_1
        }]
      }
    }
  }"

Posting with a URL (no image)

When the message contains a URL but no image, add an embed of type app.bsky.embed.external to force Bluesky to render a link preview card. Without this, the API does not automatically detect URLs or generate link cards.

Additionally, to make the URL clickable in the post text, you must add a facets entry of type app.bsky.richtext.facet#link. The byteStart and byteEnd must match the exact byte offsets of the URL in the UTF-8 encoded text.

NOW=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")
curl -s -X POST "<YOUR_PDS_URL>/xrpc/com.atproto.repo.createRecord" \
  -H "Authorization: Bearer ACCESS_JWT_HERE" \
  -H "Content-Type: application/json" \
  -d "{
    \"repo\": \"<YOUR_DID>\",
    \"collection\": \"app.bsky.feed.post\",
    \"record\": {
      \"\$type\": \"app.bsky.feed.post\",
      \"text\": \"MESSAGE_HERE\",
      \"createdAt\": \"$NOW\",
      \"facets\": [{
        \"index\": {
          \"byteStart\": BYTE_START,
          \"byteEnd\": BYTE_END
        },
        \"features\": [{
          \"\$type\": \"app.bsky.richtext.facet#link\",
          \"uri\": \"URL_HERE\"
        }]
      }],
      \"embed\": {
        \"\$type\": \"app.bsky.embed.external\",
        \"external\": {
          \"uri\": \"URL_HERE\",
          \"title\": \"TITLE_HERE\",
          \"description\": \"DESCRIPTION_HERE\"
        }
      }
    }
  }"

Note: the external.thumb field is optional — you can include a blob reference to a thumbnail image if desired. If omitted, Bluesky will render the card without a thumbnail. When posting with an image, use app.bsky.embed.images instead (see above). Do not combine external and images embeds (use app.bsky.embed.recordWithMedia if you need both).

Posting with a video URL (e.g. YouTube)

Bluesky does not auto-fetch thumbnails for external links. Without a thumb blob, the link card renders with no image. To get a proper video vignette, you must:

  1. Download the YouTube thumbnail
  2. Upload it as a blob
  3. Include the full blob reference in external.thumb
# Step 1: Download YouTube thumbnail
curl -s -o /tmp/yt_thumb.jpg "https://img.youtube.com/vi/VIDEO_ID/maxresdefault.jpg"

# Step 2: Upload as blob (use accessJwt from session)
curl -s -X POST "<YOUR_PDS_URL>/xrpc/com.atproto.repo.uploadBlob" \
  -H "Authorization: Bearer ACCESS_JWT_HERE" \
  -H "Content-Type: image/jpeg" \
  --data-binary @/tmp/yt_thumb.jpg
# → Returns: {"blob": {"$type": "blob", "ref": {"$link": "BLOB_HASH"}, "mimeType": "image/jpeg", "size": SIZE}}

# Step 3: Post with thumbnail in external embed
NOW=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")
curl -s -X POST "<YOUR_PDS_URL>/xrpc/com.atproto.repo.createRecord" \
  -H "Authorization: Bearer ACCESS_JWT_HERE" \
  -H "Content-Type: application/json" \
  -d "{
    \"repo\": \"<YOUR_DID>\",
    \"collection\": \"app.bsky.feed.post\",
    \"record\": {
      \"\$type\": \"app.bsky.feed.post\",
      \"text\": \"MESSAGE_HERE\",
      \"createdAt\": \"$NOW\",
      \"facets\": [{
        \"index\": {
          \"byteStart\": BYTE_START,
          \"byteEnd\": BYTE_END
        },
        \"features\": [{
          \"\$type\": \"app.bsky.richtext.facet#link\",
          \"uri\": \"YOUTUBE_URL_HERE\"
        }]
      }],
      \"embed\": {
        \"\$type\": \"app.bsky.embed.external\",
        \"external\": {
          \"uri\": \"YOUTUBE_URL_HERE\",
          \"title\": \"VIDEO_TITLE_HERE\",
          \"description\": \"VIDEO_DESCRIPTION_HERE\",
          \"thumb\": {
            \"\$type\": \"blob\",
            \"ref\": {
              \"\$link\": \"BLOB_HASH_FROM_STEP_2\"
            },
            \"mimeType\": \"image/jpeg\",
            \"size\": SIZE_FROM_STEP_2
          }
        }
      }
    }
  }"

Important: the thumb field must contain the full blob object (with $type, ref.$link, mimeType, and size) — not just the hash. Copy the entire blob object from the upload response.

YouTube thumbnail URL patterns:

  • Max resolution: https://img.youtube.com/vi/VIDEO_ID/maxresdefault.jpg
  • High quality: https://img.youtube.com/vi/VIDEO_ID/hqdefault.jpg
  • Standard: https://img.youtube.com/vi/VIDEO_ID/sddefault.jpg

Try maxresdefault.jpg first; fall back to hqdefault.jpg if it returns a 404.

Deleting a post

To delete a Bluesky post, extract the rkey from the post URI (the last segment after the final /):

curl -s -X POST "<YOUR_PDS_URL>/xrpc/com.atproto.repo.deleteRecord" \
  -H "Authorization: Bearer ACCESS_JWT_HERE" \
  -H "Content-Type: application/json" \
  -d '{
    "repo": "<YOUR_DID>",
    "collection": "app.bsky.feed.post",
    "rkey": "RKEY_HERE"
  }'

Notes

  • Max 300 characters (graphemes) per post
  • App password never expires
  • Session token (accessJwt) expires after ~2 hours — create a new session each time
  • Images: max 4 per post, formats jpg/png, max 1MB per image

X (Twitter)

Credentials

How to get these: Sign up at https://developer.x.com → create a project and app → go to "Keys and tokens" tab → generate Consumer Keys (API Key & Secret) and Access Token & Secret. Free tier gives you write access. See: https://developer.x.com/en/docs/authentication/oauth-1-0a

  • Developer console : https://developer.x.com/en/portal/dashboard
  • App Name : <YOUR_APP_NAME>
  • Consumer Key : <X_CONSUMER_KEY>
  • Consumer Secret : <X_CONSUMER_SECRET>
  • Access Token : <X_ACCESS_TOKEN>
  • Access Token Secret : <X_ACCESS_TOKEN_SECRET>
  • Bearer Token : <X_BEARER_TOKEN>
  • OAuth2 Client ID : <X_OAUTH2_CLIENT_ID>
  • OAuth2 Client Secret : <X_OAUTH2_CLIENT_SECRET>
  • Token expiry : never (OAuth 1.0a)

How to post

X requires OAuth 1.0a signature generation. Use this bash+python script:

AUTH_HEADER=$(python3 << 'PYEOF'
import urllib.parse, hmac, hashlib, base64, time, uuid

consumer_key = "<X_CONSUMER_KEY>"
consumer_secret = "<X_CONSUMER_SECRET>"
access_token = "<X_ACCESS_TOKEN>"
access_token_secret = "<X_ACCESS_TOKEN_SECRET>"

url = "https://api.x.com/2/tweets"
method = "POST"

oauth_params = {
    "oauth_consumer_key": consumer_key,
    "oauth_nonce": uuid.uuid4().hex,
    "oauth_signature_method": "HMAC-SHA1",
    "oauth_timestamp": str(int(time.time())),
    "oauth_token": access_token,
    "oauth_version": "1.0"
}

params_str = "&".join(f"{urllib.parse.quote(k, safe='')}={urllib.parse.quote(v, safe='')}" for k, v in sorted(oauth_params.items()))
base_string = f"{method}&{urllib.parse.quote(url, safe='')}&{urllib.parse.quote(params_str, safe='')}"
signing_key = f"{urllib.parse.quote(consumer_secret, safe='')}&{urllib.parse.quote(access_token_secret, safe='')}"
signature = base64.b64encode(hmac.new(signing_key.encode(), base_string.encode(), hashlib.sha1).digest()).decode()

oauth_params["oauth_signature"] = signature
print("OAuth " + ", ".join(f'{k}="{urllib.parse.quote(v, safe="")}"' for k, v in sorted(oauth_params.items())))
PYEOF
)

curl -s -X POST "https://api.x.com/2/tweets" \
  -H "Authorization: $AUTH_HEADER" \
  -H "Content-Type: application/json" \
  -d '{"text":"MESSAGE_HERE"}' | python3 -m json.tool

Posting with an image

Doc: https://developer.x.com/en/docs/x-api/tweets/manage-tweets/api-reference/post-tweets (media) + https://developer.x.com/en/docs/x-api/media/upload-media/api-reference

  1. Upload the image via media upload endpoint → get a media_id
  2. Create the tweet referencing the media_id

Note: media upload still uses the v1.1 endpoint. The OAuth 1.0a signature must be generated for the upload URL.

Notes

  • Max 280 characters per post
  • Free tier : 1,500 tweets/month, credits reset monthly
  • Tokens never expire — no refresh needed
  • If you get CreditsDepleted error, wait for monthly credit reset
  • OAuth2 credentials stored above for reference but OAuth 1.0a is preferred (simpler, no expiry)

Posting guidelines for AI agents

  1. Always confirm with the user before posting publicly
  2. Character limits : respect each platform's limit (280 X, 300 Bluesky, 500 Mastodon, ~3000 LinkedIn)
  3. LinkedIn token : check expiry date before posting. If expired, guide user through refresh procedure
  4. Bluesky : always create a fresh session before posting (step 1 then step 2)
  5. X : if credits are depleted, inform the user and skip X
  6. Test posts : use visibility=direct on Mastodon, visibility=CONNECTIONS + feedDistribution=NONE on LinkedIn. Bluesky and X have no private post option — warn the user
  7. Hashtags : supported natively on Mastodon and X. LinkedIn uses them in the commentary text. Bluesky requires facet annotations for clickable hashtags (plain #text works as display only)
  8. YouTube / video URLs on LinkedIn : the basic content.article block does NOT reliably render a video thumbnail. Always download the YouTube thumbnail, upload it to LinkedIn, and pass the image URN in content.article.thumbnail. See the "Posting with a video URL" section under LinkedIn
  9. YouTube / video URLs on Bluesky : Bluesky does NOT auto-fetch thumbnails for external links. Always download the YouTube thumbnail, upload it as a blob, and include the full blob reference in external.thumb. See the "Posting with a video URL" section under Bluesky

How to provide an image to the AI agent

When asking the agent to post with an image, provide one of:

  • A local file path : e.g. ~/Pictures/my-image.png — the agent will use this path directly in the curl upload commands
  • A URL : the agent will download it first to a temp file, then upload it
  • A file in the vault : reference it like Attachments/my-image.png — the agent will resolve the full path

Supported formats: JPG, PNG (all platforms). GIF supported on Mastodon and X only. Recommended: provide a square or 16:9 image, at least 1200x675px for best rendering across platforms. Always provide alt text for accessibility — the agent should ask for it if not provided.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment