Authentication
Every request is authenticated with an API key in the standard Authorization header. A key belongs to exactly one workspace, and every call is scoped to that workspace.
Authorization: Bearer pm_live_<your-key>Base URL: https://postclay.com/api. Missing key → 401 MISSING_API_KEY; unknown or revoked → 401 INVALID_API_KEY.
Create an API key
Create a key from the dashboard, or via the API. The secret is shown once — store it now.
POST https://postclay.com/api/api-keys
Authorization: Bearer <session>
Content-Type: application/json
{ "name": "My integration" }
# 201 Created — the raw key appears only in this response
{ "key": "pm_live_ab12cd34…" }GET /api-keys lists keys (never the secret): each has id, name, prefix, lastUsedAt, revokedAt, createdAt. DELETE /api-keys/:id revokes a key and returns 204.
Create a post
Creates a post in DRAFT. targets is required — one entry per connected account you want to publish to. Passing scheduledAt here stores the intent but does not schedule; call schedule to enqueue.
POST https://postclay.com/api/posts
{
"content": "Hello from the Postclay API",
"kind": "SINGLE", // SINGLE | CAROUSEL | THREAD
"mediaIds": ["media-uuid"], // optional, ordered, ≤ 50
"labels": ["launch"], // optional
"targets": [
{ "socialAccountId": "account-uuid",
"contentOverride": null, // null = inherit "content"
"mediaIds": null } // null = inherit; [] = no media
]
}Response — the created post (truncated):
201 Created
{
"id": "post-uuid",
"status": "DRAFT",
"kind": "SINGLE",
"content": "Hello from the Postclay API",
"mediaIds": ["media-uuid"],
"targets": [
{ "id": "target-uuid", "socialAccountId": "account-uuid",
"platform": "X", "status": "PENDING", "attempts": 0,
"externalPostId": null, "permalink": null }
],
"createdAt": "2026-08-20T09:00:00.000Z"
}curl
curl https://postclay.com/api/posts \
-H "Authorization: Bearer pm_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"content": "Hello from the Postclay API",
"kind": "SINGLE",
"targets": [{ "socialAccountId": "account-uuid" }]
}'Schedule a post
Moves a draft to SCHEDULED and enqueues each target. Both fields are required. scheduledAt is absolute when it carries a Z or offset, otherwise it is wall-clock time in timezone.
POST https://postclay.com/api/posts/:id/schedule
{ "scheduledAt": "2026-08-20T18:30:00Z", "timezone": "Asia/Riyadh" }
# 200 OK — the full post, status now "SCHEDULED"List posts
Returns a paginated envelope. Filter by status, label, or date range.
GET https://postclay.com/api/posts?status=scheduled&page=1&pageSize=20
# status: scheduled | published | draft | failed
# label: string from,to: ISO-8601 pageSize: 1–100 (default 20)
{ "data": [ /* posts */ ], "total": 42, "page": 1, "pageSize": 20 }Delete a post
Cancels any pending targets and deletes the post.
DELETE https://postclay.com/api/posts/:id
# 204 No ContentIdempotency
Postclay does not accept a client Idempotency-Key header today — treat POST /posts as create-once and store the returned id. Retries are safe downstream: each target carries an internal, server-generated idempotency value so the publish workers never double-post to a platform on retry. (A client-facing idempotency key is on the roadmap; this note will change when it ships.)
Webhooks
Subscribe a URL to publish events. Two events fire: POST_PUBLISHED and POST_FAILED.
POST https://postclay.com/api/webhooks
{ "url": "https://you.example/hook",
"events": ["POST_PUBLISHED", "POST_FAILED"] }
# 201 — response includes "secret" once (used to verify signatures)Delivered payloads:
// POST_PUBLISHED
{ "event": "POST_PUBLISHED", "occurredAt": "2026-08-20T12:00:00.000Z",
"data": { "postId": "…", "targetId": "…", "platform": "X",
"externalId": "…", "permalink": "https://…" } }
// POST_FAILED
{ "event": "POST_FAILED", "occurredAt": "2026-08-20T12:00:00.000Z",
"data": { "postId": "…", "targetId": "…", "platform": "X",
"error": "…" } }Every delivery is signed. Verify the HMAC before trusting a payload:
Content-Type: application/json
X-Postclay-Signature: sha256=<hex hmac of the raw body, keyed by your secret>
X-Postclay-Event: POST_PUBLISHED
X-Postclay-Delivery: <delivery-uuid>
# verify (Node):
const mac = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
const ok = "sha256=" + mac === req.headers["x-postclay-signature"];Deliveries retry up to 5 times with exponential backoff (10s per-request timeout); internal/loopback URLs are refused. POST /webhooks/:id/test sends a test delivery (202); POST /webhooks/:id/rotate-secret issues a new secret.
Rate limits & quotas
There are no per-plan API rate limits. Two real limits apply:
Per-IP limits guard unauthenticated and AI endpoints (e.g. signup, login, caption/hashtag generation). The authenticated /posts, /media, /webhooks, /api-keys and /mcp endpoints are not IP-limited. On exhaustion you get:
429 { "error": { "code": "RATE_LIMITED", "messageKey": "errors.rateLimited" } }Per-plan quotas cap connected accounts and team seats (posts are unlimited on every tier):
| Plan | Connected accounts | Seats | Scheduled posts |
|---|---|---|---|
| Trial | 25 | 10 | Unlimited |
| Builder | 8 | 3 | Unlimited |
| Team | 25 | 10 | Unlimited |
| Scale | Unlimited | Unlimited | Unlimited |
A “connected account” is one social identity linked to your workspace (a specific X handle, Instagram business account, Facebook page, and so on). Reconnecting the same identity reuses its slot.
MCP — connect your agent
Postclay is an MCP server, so any MCP client can create, schedule, list and cancel posts as a first-class user. The endpoint is stateless Streamable-HTTP and takes the same API key:
POST https://postclay.com/api/mcp
Authorization: Bearer pm_live_<your-key>Claude Code
Add Postclay to your MCP config:
{
"mcpServers": {
"postclay": {
"url": "https://postclay.com/api/mcp",
"headers": { "Authorization": "Bearer pm_live_<your-key>" }
}
}
}Cursor
Add the same server block to Cursor’s MCP settings (~/.cursor/mcp.json or Settings → MCP). The url and Authorization header are identical to the snippet above.
Tools
The server exposes eight tools:
list_platforms() list_accounts()
create_post({ content, kind: "SINGLE"|"CAROUSEL",
targets: [{ socialAccountId }], mediaIds?, timezone?, recurrence? })
schedule_post({ postId, scheduledAt, timezone })
list_posts({ status?, from?, to?, page?, pageSize? })
get_post({ postId }) cancel_post({ postId })
upload_media({ filename, mimeType, dataBase64 })A ready-made skill file is published at postclay.com/skill.md — point OpenClaw / Hermes-style agents at it and they pick up these tools natively.
Questions? [email protected] · Back to home