PostThatLater Skill
Schedule and manage social media posts from Claude Code or any AI assistant.
Setup
1. Create an account
Sign up at https://postthatlater.com — a subscription is required to use the API.
2. Generate an API key
In PostThatLater: Account Settings → API Keys → Create new key
Copy the sk_ptl_... key — it's shown only once.
3. Set the environment variable
CODEBLOCK0
Add to ~/.zshrc or ~/.bashrc to persist across sessions.
4. Verify
CODEBLOCK1
You should see your connected social media accounts.
Base URL
CODEBLOCK2
All API requests require:
Authorization: Bearer $PTL_API_KEY
Content-Type: application/json (for POST/PATCH)
Critical Tips for Agents
- 1. Call
GET /api/v1/accounts first — you need numeric account IDs to schedule posts. Never guess them. - Ask for timezone once per session — all
scheduled_at values must be ISO 8601 UTC (e.g. 2026-03-10T09:00:00Z). Convert from the user's local time. - Confirm before
publish_now — publishing is immediate and irreversible. Always confirm with the user before calling the publish-now endpoint. - Use
Idempotency-Key on every POST/PATCH — generate a UUID per request to prevent duplicate posts on retry. Example: Idempotency-Key: $(uuidgen | tr '[:upper:]' '[:lower:]'). - Check platform limits — always call
GET /api/v1/platforms to get the live list of available platforms and their limits. Never hardcode a platform list or assume a platform is available. - Cross-posting — pass multiple IDs in
account_ids to post the same content everywhere at once. The API creates one post record per account and links them via group_id. - All times are UTC in responses — convert to the user's timezone when displaying scheduled times.
- Posting with an image — first
POST /api/v1/images with the image URL to get a filename, then pass that filename in the images array of POST /api/v1/posts. Use GET /api/v1/images to list already-stored images that can be reused without re-uploading.
Accounts
List all connected accounts
CODEBLOCK4
Response:
CODEBLOCK5
INLINECODE16 is "connected" (healthy) or "error" (needs reconnecting in dashboard).
Get a single account
CODEBLOCK6
Posts
List posts
CODEBLOCK7
Query parameters:
- -
status — pending, posted, or failed (omit for all) - INLINECODE23 — e.g.
bluesky, mastodon, INLINECODE26 - INLINECODE27 — max results (default: 20, max: 100)
- INLINECODE28 — skip N results for pagination (default: 0)
Response:
CODEBLOCK8
Create a post (schedule)
CODEBLOCK9
Body parameters:
- -
text (required) — post content; must not exceed platform char limit - INLINECODE30 (required) — ISO 8601 UTC datetime; must be in the future
- INLINECODE31 (required) — array of account IDs (at least one)
- INLINECODE32 — optional array of image filenames
Returns HTTP 201 with the first created post. All posts share a group_id.
Get a single post
CODEBLOCK10
Update a post
Only works on pending posts. Returns 409 conflict if already published or failed.
CODEBLOCK11
Delete a post
Cannot delete posted posts (returns 409). Failed posts can be deleted.
CODEBLOCK12
Response:
CODEBLOCK13
Publish immediately
Bypasses the scheduler. Post must be pending or failed. Irreversible — confirm with user first.
CODEBLOCK14
Response:
{
"data": {
"id": 101,
"status": "posted",
"platform_post_url": "https://bsky.app/profile/you.bsky.social/post/...",
...
},
"meta": { "request_id": "..." }
}
Images
Post with images by first ingesting the image from a URL, then passing the returned filename when creating a post.
Ingest an image from a URL
Downloads, processes (EXIF rotation, compression, format conversion), and stores the image server-side. Returns a filename to use in posts.
CODEBLOCK16
Response:
CODEBLOCK17
Pass the filename in the images array of POST /api/v1/posts. Check GET /api/v1/platforms for the max_images limit per platform.
List stored images
Returns all images referenced by your posts with file metadata and which post IDs use each image. Use this to reuse an already-uploaded image without re-ingesting.
CODEBLOCK18
Response:
{
"data": [
{
"filename": "1741036800000-123456789.jpeg",
"url": "/uploads/1741036800000-123456789.jpeg",
"size": 284672,
"created_at": "2026-03-01T09:00:00.000Z",
"referenced_by_post_ids": [101, 104]
}
],
"meta": { "request_id": "...", "total": 1 }
}
Analytics
Overall posting health summary
CODEBLOCK20
Query parameters: period — 7d, 30d, 90d, or all (default: 30d)
Response:
CODEBLOCK21
Top posts by engagement
CODEBLOCK22
Query parameters:
- -
period — 7d, 30d, or all (default: 30d) - INLINECODE57 — number of posts (default: 5, max: 20)
Per-post metrics
CODEBLOCK23
Response:
CODEBLOCK24
Breakdown by platform
CODEBLOCK25
Timeline (daily post counts)
CODEBLOCK26
Platform Capabilities
Public endpoint — no authentication required.
Returns character limits and capabilities for all currently available platforms.
New platforms may be added over time, so always fetch this list rather than
hardcoding platform names or limits.
CODEBLOCK27
Response fields per platform:
- -
name — slug used in API calls (e.g. bluesky) - INLINECODE60 — human-readable name
- INLINECODE61 — maximum post length in characters
- INLINECODE62 — maximum images per post
- INLINECODE63 — whether video uploads are accepted
- INLINECODE64 — platform-specific connection or usage notes
---
## Error Handling
All errors follow:
json
{
"error": {
"code": "validation_error",
"message": "Validation failed.",
"details": [
{ "field": "text", "message": "Text exceeds Bluesky character limit (300 chars)." },
{ "field": "scheduled
at", "message": "scheduledat must be a future datetime." }
]
}
}
| Code | HTTP | Meaning |
|------|------|---------|
| `invalid_api_key` | 401 | Missing, malformed, or revoked Bearer token |
| `subscription_required` | 402 | No active subscription |
| `not_found` | 404 | Resource doesn't exist or belongs to another user |
| `conflict` | 409 | Action not allowed in current state (e.g. editing a published post) |
| `validation_error` | 400 | Request body failed validation — check `details` array for field-level errors |
| `rate_limited` | 429 | 60 req/min per key — check `Retry-After` header |
| `internal_error` | 500 | Unexpected server error |
---
## Example Session
User: Schedule a post about our summer sale on Bluesky and Mastodon for tomorrow at 10am EST
Agent workflow:
- 1. GET /api/v1/accounts → find Bluesky (id: 12) and Mastodon (id: 15)
- GET /api/v1/platforms → confirm char limits (300 / 500)
- Convert 10am EST → 15:00 UTC (March 5 → 2026-03-05T15:00:00Z)
- Confirm with user: "Ready to schedule 'Summer sale...' to Bluesky + Mastodon for Mar 5 at 10am EST?"
- POST /api/v1/posts with account_ids: [12, 15] and Idempotency-Key header
- Report back: "Scheduled! Post #101 and #102 will go out March 5 at 10am EST."
```
PostThatLater 技能
从 Claude Code 或任何 AI 助手安排和管理社交媒体帖子。
设置
1. 创建账户
在 https://postthatlater.com 注册 — 使用 API 需要订阅。
2. 生成 API 密钥
在 PostThatLater 中:账户设置 → API 密钥 → 创建新密钥
复制 skptl... 密钥 — 它只显示一次。
3. 设置环境变量
bash
export PTLAPIKEY=skptlyourkeyhere
添加到 ~/.zshrc 或 ~/.bashrc 以在会话间持久化。
4. 验证
bash
curl https://postthatlater.com/api/v1/accounts \
-H Authorization: Bearer $PTLAPIKEY
你应该能看到已连接的社交媒体账户。
基础 URL
https://postthatlater.com
所有 API 请求都需要:
Authorization: Bearer $PTLAPIKEY
Content-Type: application/json (用于 POST/PATCH)
给代理的关键提示
- 1. 先调用 GET /api/v1/accounts — 你需要数字账户 ID 来安排帖子。永远不要猜测它们。
- 每个会话询问一次时区 — 所有 scheduledat 值必须是 ISO 8601 UTC 格式(例如 2026-03-10T09:00:00Z)。从用户的本地时间转换。
- 在 publishnow 前确认 — 发布是即时且不可逆的。在调用立即发布端点前务必与用户确认。
- 在每个 POST/PATCH 上使用 Idempotency-Key — 为每个请求生成一个 UUID 以防止重试时重复发帖。示例:Idempotency-Key: $(uuidgen | tr [:upper:] [:lower:])。
- 检查平台限制 — 始终调用 GET /api/v1/platforms 获取可用平台的实时列表及其限制。永远不要硬编码平台列表或假设某个平台可用。
- 跨平台发布 — 在 accountids 中传递多个 ID 以一次性将相同内容发布到所有平台。API 为每个账户创建一个帖子记录,并通过 groupid 链接它们。
- 响应中所有时间均为 UTC — 在显示预定时间时转换为用户的时区。
- 带图片发帖 — 首先使用图片 URL 调用 POST /api/v1/images 获取文件名,然后在 POST /api/v1/posts 的 images 数组中传递该文件名。使用 GET /api/v1/images 列出已存储的图片,这些图片可以重复使用而无需重新上传。
账户
列出所有已连接的账户
bash
curl https://postthatlater.com/api/v1/accounts \
-H Authorization: Bearer $PTLAPIKEY
响应:
json
{
data: [
{
id: 12,
platform: bluesky,
handle: you.bsky.social,
display_name: you.bsky.social,
status: connected,
created_at: 2026-01-10T08:30:00.000Z,
updated_at: 2026-01-10T08:30:00.000Z
}
],
meta: { request_id: ... }
}
status 为 connected(正常)或 error(需要在仪表盘中重新连接)。
获取单个账户
bash
curl https://postthatlater.com/api/v1/accounts/12 \
-H Authorization: Bearer $PTLAPIKEY
帖子
列出帖子
bash
curl https://postthatlater.com/api/v1/posts?status=pending&limit=10&offset=0 \
-H Authorization: Bearer $PTLAPIKEY
查询参数:
- - status — pending、posted 或 failed(省略则返回全部)
- platform — 例如 bluesky、mastodon、linkedin
- limit — 最大结果数(默认:20,最大:100)
- offset — 跳过 N 个结果用于分页(默认:0)
响应:
json
{
data: [
{
id: 101,
text: Hello from the API!,
scheduled_at: 2026-03-15T09:00:00.000Z,
status: pending,
platform: bluesky,
account_id: 12,
images: [],
platformpostid: null,
platformposturl: null,
error_message: null,
retry_count: 0,
likes: 0,
comments: 0,
shares: 0,
group_id: null,
created_at: 2026-03-01T12:00:00.000Z,
updated_at: null
}
],
meta: { request_id: ..., total: 5, limit: 10, offset: 0 }
}
创建帖子(安排)
bash
curl -X POST https://postthatlater.com/api/v1/posts \
-H Authorization: Bearer $PTLAPIKEY \
-H Content-Type: application/json \
-H Idempotency-Key: $(uuidgen | tr [:upper:] [:lower:]) \
-d {
text: Hello from the PostThatLater API!,
scheduled_at: 2026-03-15T09:00:00.000Z,
account_ids: [12, 15]
}
请求体参数:
- - text (必填) — 帖子内容;不得超过平台字符限制
- scheduledat (必填) — ISO 8601 UTC 日期时间;必须是未来时间
- accountids (必填) — 账户 ID 数组(至少一个)
- images — 可选的图片文件名数组
返回 HTTP 201 及第一个创建的帖子。所有帖子共享一个 group_id。
获取单个帖子
bash
curl https://postthatlater.com/api/v1/posts/101 \
-H Authorization: Bearer $PTLAPIKEY
更新帖子
仅对 pending 状态的帖子有效。如果已发布或失败,返回 409 conflict。
bash
curl -X PATCH https://postthatlater.com/api/v1/posts/101 \
-H Authorization: Bearer $PTLAPIKEY \
-H Content-Type: application/json \
-H Idempotency-Key: $(uuidgen | tr [:upper:] [:lower:]) \
-d {
text: Updated content for the post,
scheduled_at: 2026-03-16T10:00:00.000Z
}
删除帖子
不能删除 posted 状态的帖子(返回 409)。失败的帖子可以删除。
bash
curl -X DELETE https://postthatlater.com/api/v1/posts/101 \
-H Authorization: Bearer $PTLAPIKEY
响应:
json
{ data: { deleted: true, id: 101 }, meta: { request_id: ... } }
立即发布
绕过调度器。帖子必须是 pending 或 failed 状态。不可逆 — 先与用户确认。
bash
curl -X POST https://postthatlater.com/api/v1/posts/101/publish-now \
-H Authorization: Bearer $PTLAPIKEY
响应:
json
{
data: {
id: 101,
status: posted,
platformposturl: https://bsky.app/profile/you.bsky.social/post/...,
...
},
meta: { request_id: ... }
}
图片
通过先从 URL 获取图片,然后在创建帖子时传递返回的文件名来发布带图片的帖子。
从 URL 获取图片
下载、处理(EXIF 旋转、压缩、格式转换)并在服务器端存储图片。返回一个用于帖子的 filename。
bash
curl -X POST https://postthatlater.com/api/v1/images \
-H Authorization: Bearer $PTLAPIKEY \
-H Content-Type: application/json \
-d {url: https://example.com/photo.jpg}
响应:
json
{
data: { filename: 174103680