Posts

Suggest covers

Suggest covers from a post's media: frames picked from its video, or — without a video — the best first image among 2+ images, ranked (rank 1 is best) with a one-line reason. Videos take two calls: the first starts frame extraction (status queued / running, about 10–30 s), call again after retryAfterSec to get ranked candidates; image posts come back ranked right away. Idempotent per media, so repeat calls are not billed again and never use the monthly AI quota. Only the post's shared media is used, and it must be stored on popkit. This endpoint changes nothing; apply a pick with Set cover.

POST/api/v1/posts/{postId}/cover-suggestions
posts:writewrite tierIdempotency-Key optional

Authorization & guardrails

  • Send Authorization: Bearer <credential>; credentials are issued under Settings → Integrations → Long-lived credentials, see Authentication.
  • The credential needs scope posts:write; the issuer's workspace role must also allow the action.
  • Rate limit: write tier, 10/min per credential; over the cap returns 429 with Retry-After.
  • Idempotency: honoured when Idempotency-Key is present; optional otherwise.

Path parameters

postIdstringrequired

Post id.

Request body (application/json)

languageenum: zh · enoptional

Language of the reasons; defaults to Chinese and leaves existing reasons alone. If given and different from the existing reasons, only the reasons are rewritten — the ranking stays.

clientRequestIdstringoptional

Idempotency key (8–128 chars of [A-Za-z0-9_.:-]); prefer the Idempotency-Key header, which wins over the body.

Rules

  • Each workspace can extract frames for up to 50 videos a day; beyond that you get rate_limited (params.reason = cover_daily_cap, retryAfterSec until the next UTC midnight).
  • Candidates are cleaned up after 7 days; calling again after that generates a fresh set.

Responses

Success is HTTP 200 with a { data, meta } envelope; examples per status are on the right (at the end of the page on narrow screens). Every error is { error: { code, message, params }, meta } — branch on the code, not the message; see Errors.

StatusPossible codes
400invalid_input
401unauthenticated
402plan_limit
403forbidden / workspace_not_found
404not_found
409no_account / needs_reauth / replay
429rate_limited
500internal
502platform_error
503external_writes_off

Examples

Request
curl -X POST "https://popkit.loxily.com/api/v1/posts/pst_01J8ZK3R7QW6M2/cover-suggestions" \
  -H "Authorization: Bearer popkit_it_••••••••" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: acme-2026-09-21-001" \
  -d '{
  "language": "zh"
}'
Response
Video: extracting
{
  "data": {
    "postId": "pst_01J8ZK3R7QW6M2",
    "suggestionId": "3f2b9c1e-6d0a-4e8b-9a7c-5b1d2e3f4a60",
    "status": "queued",
    "sourceKind": "video",
    "engine": null,
    "candidates": [],
    "retryAfterSec": 5,
    "next": "frames are still being extracted from the video (usually 10–30 s); call suggest_covers again with the same postId in 5s"
  },
  "meta": {
    "requestId": "6a1d0c9e-2b7f-4e3a-8c5d-1f0e9b8a7c81"
  }
}