/api/v1/meCurrent key, workspace and limits
Responses
- 200Me · The key and its workspace.
- 401Missing, unknown or revoked API key.
- 429Too many requests; wait
Retry-Afterseconds.
DevelopersBeta
The postyay REST API and MCP server do what the app does: upload media, run the same pre-flight checks, schedule to TikTok and Bluesky, and follow each post until it’s live. All you need is an API key.
BetaThe API and the MCP server are in beta and included in every plan while the beta lasts. The v1 contract only grows: fields are added, never renamed or removed.
Five steps from zero to a published post. Every example uses curl; set your key once with export POSTYAY_API_KEY=pyk_live_….
Go to Settings → API keys, give the key a name (e.g. “Zapier” or “Claude”) and copy it. You’ll only see it once; we store just a hash.
Connect TikTok or Bluesky in the app first. Each account’s id is what you post to; health tells you if it needs reconnecting.
curl https://postyay.com/api/v1/accounts \
-H "Authorization: Bearer $POSTYAY_API_KEY"Send the file as multipart, or give us a public URL and we download it. Keep the returned id. We convert the format for each platform when it publishes.
curl https://postyay.com/api/v1/media \
-H "Authorization: Bearer $POSTYAY_API_KEY" \
-F [email protected]
# or let postyay download it
curl https://postyay.com/api/v1/media \
-H "Authorization: Bearer $POSTYAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "url": "https://example.com/clip.mp4" }'One post, several accounts. TikTok needs settings.privacyLevel. schedule is "now", an ISO 8601 time, or "draft". The same pre-flight checks as the app run first, and anything that would fail comes back as violations.
curl https://postyay.com/api/v1/posts \
-H "Authorization: Bearer $POSTYAY_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"text": "Behind the scenes of the shoot #bts",
"mediaIds": ["MEDIA_ID"],
"targets": [
{ "accountId": "TIKTOK_ACCOUNT_ID",
"settings": { "privacyLevel": "PUBLIC_TO_EVERYONE", "allowComment": true } },
{ "accountId": "BLUESKY_ACCOUNT_ID",
"settings": { "altText": { "MEDIA_ID": "A camera crew on set" } } }
],
"schedule": "now"
}'
# "schedule": "2026-10-01T09:00:00+03:00" schedules it, "draft" only saves it.Poll the post every few seconds. When status is published, each target has its live url. TikTok videos can take a few minutes to process.
while :; do
status=$(curl -s https://postyay.com/api/v1/posts/POST_ID \
-H "Authorization: Bearer $POSTYAY_API_KEY" | jq -r .status)
echo "$status"
case "$status" in published|failed|needs_action|canceled) break ;; esac
sleep 5
done{
"id": "5b0e…",
"status": "published",
"source": "api",
"targets": [
{ "platform": "tiktok", "status": "published",
"url": "https://www.tiktok.com/@you/video/7431…" },
{ "platform": "bluesky", "status": "published",
"url": "https://bsky.app/profile/you.bsky.social/post/3l…" }
]
}Send the key in the Authorization header of every request: Bearer pyk_live_….
Every error has the same shape. requestId is also in the x-trace-id header: quote it when you contact support.
{
"code": "PREFLIGHT",
"message": "TikTok allows 2,200 characters — you're 100 over.",
"requestId": "3f0c9d2e-…",
"violations": [
{ "accountId": "…", "platform": "tiktok", "code": "text_too_long",
"message": "TikTok allows 2,200 characters — you're 100 over.",
"params": { "max": 2200, "over": 100 },
"fix": { "mode": "manual", "action": "shorten_text" } }
]
}VALIDATION_ERROR adds details (the fields that are wrong). PREFLIGHT adds violations: one per problem, with the account, the platform and a stable code such as text_too_long or media_required.
Limits are per key, in one-minute windows: 60 requests a minute, of which at most 10 may create posts. MCP tool calls count against the same budget.
Every response says where you stand with RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset (seconds) and RateLimit-Policy. Over the limit you get 429 RATE_LIMITED with Retry-After.
RateLimit-Policy: 60;w=60, 10;w=60;comment="posts"
RateLimit-Limit: 60
RateLimit-Remaining: 0
RateLimit-Reset: 23
Retry-After: 23Posts also count toward your plan’s monthly post limit, exactly like posts made in the app. GET /api/v1/me shows your usage.
Lists return { data, nextCursor }, newest first. Pass limit (1–100, default 25) and, for the next page, cursor=nextCursor. nextCursor is null on the last page. GET /api/v1/posts also filters by status, accountId, from and to.
curl "https://postyay.com/api/v1/posts?status=scheduled&limit=50" \
-H "Authorization: Bearer $POSTYAY_API_KEY"
# next page
curl "https://postyay.com/api/v1/posts?status=scheduled&limit=50&cursor=NEXT_CURSOR" \
-H "Authorization: Bearer $POSTYAY_API_KEY"Network hiccups happen. Send an Idempotency-Key header (any unique string, e.g. a UUID) with POST /api/v1/posts and retry freely: for 24 hours, the same key with the same body returns the first result with Idempotent-Replayed: true instead of creating a second post. The same key with a different body is refused.
A post goes to one or more accounts; each is a target with its own status. The post’s status is the most urgent of its targets’.
postyay speaks the Model Context Protocol, so AI assistants can schedule posts for you: “post this clip to TikTok and Bluesky tomorrow at 9”. It uses the same API key, limits and checks as the REST API.
Endpoint
https://postyay.com/mcp
Streamable HTTP (stateless, JSON responses). Authentication: Authorization: Bearer pyk_live_….
One command adds the server with your key:
claude mcp add --transport http postyay https://postyay.com/mcp \
--header "Authorization: Bearer pyk_live_…"Add this to claude_desktop_config.json (Settings → Developer → Edit config) and restart Claude. mcp-remote bridges the remote server and sends your key as a header.
{
"mcpServers": {
"postyay": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://postyay.com/mcp",
"--header", "Authorization:${POSTYAY_API_KEY}"],
"env": { "POSTYAY_API_KEY": "Bearer pyk_live_…" }
}
}
}Any client that lets you set HTTP headers on a remote MCP server works with the URL and the header:
{
"mcpServers": {
"postyay": {
"url": "https://postyay.com/mcp",
"headers": { "Authorization": "Bearer pyk_live_…" }
}
}
}Their custom connectors sign in with OAuth (or connect without any authentication), and can’t send an API key. postyay’s MCP server uses API keys only for now, so these connectors aren’t supported yet. OAuth sign-in is planned. Until then, use Claude Desktop, Claude Code or another client that sends headers.
Generated from the same schemas the API validates with. Base URL: https://postyay.com/api/v1. Import the OpenAPI document into Postman, Insomnia or your code generator.
/api/v1/meRetry-After seconds./api/v1/accountsEvery connected account with its health. Use the id as a post target.
Retry-After seconds./api/v1/mediaSend the file as multipart/form-data (one file part), or JSON { "url": "https://…" } and we download it. Videos (MP4, MOV, WebM), photos (JPEG, PNG, WebP, GIF, HEIC, AVIF) and PDFs. Conversion for each platform happens at publish time.
VALIDATION_ERROR (see details), PREFLIGHT (see violations), or another request problem.Retry-After seconds./api/v1/media/uploadsFor big videos: returns a presigned URL; PUT the file there with the given headers, then call POST /media/{id}/complete. Available when postyay stores media in object storage; otherwise 409 DIRECT_UPLOAD_UNAVAILABLE (use POST /media).
VALIDATION_ERROR (see details), PREFLIGHT (see violations), or another request problem.Retry-After seconds./api/v1/media/{id}/completeChecks what arrived, reads the file’s details and marks it ready. Safe to call again.
VALIDATION_ERROR (see details), PREFLIGHT (see violations), or another request problem.Retry-After seconds./api/v1/media/{id}Retry-After seconds./api/v1/postsNewest first, paginated with cursor.
nextCursor from the previous page.VALIDATION_ERROR (see details), PREFLIGHT (see violations), or another request problem.Retry-After seconds./api/v1/postsValidates accounts, media and per-platform settings, runs pre-flight (text length, media type, duration, aspect…) for every target, and schedules it. Problems come back as 400 PREFLIGHT with violations. Send an Idempotency-Key header to retry safely: the same key and body within 24 hours returns the first result (Idempotent-Replayed: true).
VALIDATION_ERROR (see details), PREFLIGHT (see violations), or another request problem.PLAN_LIMIT_POSTS).Retry-After seconds./api/v1/posts/{id}Each target’s status, timeline and, once published, its live url.
Retry-After seconds./api/v1/posts/{id}Send only what changes, e.g. { "schedule": "2026-10-01T09:00:00Z" }. Pre-flight runs again. 409 NOT_EDITABLE once a target is publishing or published.
VALIDATION_ERROR (see details), PREFLIGHT (see violations), or another request problem.PLAN_LIMIT_POSTS).Retry-After seconds./api/v1/posts/{id}Cancels every target that hasn’t published. Already-published posts stay on the platform. 409 IN_FLIGHT while it is publishing.
Retry-After seconds.http(s) URL of a video, photo or PDF. Redirects are followed; private addresses are refused.video/mp4.yourBrand and/or brandedContent. Default: false.SELF_ONLY. Default: false.GET /v1/accounts.TikTokSettings (TikTok needs privacyLevel) and BlueskySettings. Default: {}.POST /v1/media, in order. Default: []."now" publishes within seconds, an ISO 8601 time schedules it, "draft" saves it without scheduling (drafts may be incomplete).null removes it."now" publishes within seconds, an ISO 8601 time schedules it, "draft" saves it without scheduling (drafts may be incomplete).text_too_long, video_too_long, media_required.manual: change the content. confirm: we can fix it in the app (crop, trim) once you agree.VALIDATION_ERROR, PREFLIGHT, RATE_LIMITED, NOT_FOUND.x-trace-id header.VALIDATION_ERROR.@username on TikTok and Bluesky.reconnect_required: posts to it can’t publish until it is reconnected in the app.ready media can be attached to a post.draft → scheduled → queued → preparing → publishing → processing → published; or needs_action / failed / canceled.needs_action beats published).cursor for the next page; null on the last one.cursor for the next page; null on the last one.null: unlimited.