REST API

에러

모양은 하나, 코드는 구체적으로, 값은 다룰 수 있게.

모양

실패는 code 와 영어 message 를 가진 error 객체로 옵니다. message 에 끼워 넣은 값은 따로 필드로도 보냅니다. 그 값으로 자기 언어의 문장을 다시 만들 수 있습니다.

Idempotency-Key 헤더는 아직 안 받습니다. 대신 내용 지문으로 중복을 잡습니다. 같은 글과 파일이 같은 계정으로 10분 안에 다시 오면 duplicate_post 로 거절합니다. 의도한 재발행이면 force: true 를 주세요.

위반이 여럿이면 한 번에 옵니다

규칙을 둘 이상 어기면 error.errors 에 전부 담기고, 첫 항목은 error 자리에도 그대로 펼쳐집니다. 한 번에 고치면 됩니다. 재시도할 때마다 다음 위반을 하나씩 듣지 않아도 됩니다. 둘만 예외로 혼자 옵니다 — no_accounts 와 empty_content 는 나머지 검사의 전제입니다.

{
  "error": {
    "code": "content_too_long",
    "message": "Threads allows 500 characters. This is 620.",
    "platform": "threads",
    "limit": 500,
    "actual": 620
  }
}

코드

이름타입설명
verification_required401Email and phone verification are not finished.
invalid_api_key401The bearer token is not a valid key.
forbidden_scope403The key lacks the scope. Sends scope.
no_accounts422accountIds was empty. Sends candidates, and next when nothing is connected. Sends needsReconnect and a reconnect hint when the channels you have are expired rather than missing.
empty_content422Neither text nor media was given.
account_not_connected404One of the accountIds is not connected. Sends candidates.
account_expired422The channel token expired. Sends platform.
platform_not_available422That channel is not live yet. Sends platform.
content_too_long422Sends platform, limit and actual.
media_required422That channel cannot post text alone. Sends platform.
media_mixed422Images and video in one post. Sends platform.
media_too_many422Sends platform, limit and actual.
media_too_large422Sends limit, limitMb and actual.
media_aspect_ratio422Sends limit and actual.
media_dimensions422Video wider than the limit. Sends limit and actual. Image width is never a reason.
media_type_not_supported422Sends types.
media_too_long422Video longer than the limit. Sends limit and actual.
media_url_invalid422Not an https URL.
media_url_blocked422That address is private, loopback or link-local.
media_url_unreachable422The host did not answer, or redirected too many times.
workspace_required400This account has more than one workspace and the request did not say which. Sends workspaces, each with id, name, slug and channels, so you can match what the person said without extra calls. Pass the x-uplika-workspace header, or the workspaceId argument on MCP tools.
workspace_mismatch404The post, channel, or media is in a different workspace on the same account. Sends workspaces. Retry there instead of connecting the account again.
media_not_uploaded404media_complete was never called.
media_expired404The upload was reclaimed. Upload the file again.
media_not_configured503Media storage is not set up.
post_not_found404No such publish in this workspace.
media_not_found404No such media in this workspace.
key_not_found404No such API key.
grant_not_found404No such authorization.
nothing_to_retry422Every target already succeeded.
not_published422The post has not gone out yet.
platform_error502The channel refused or did not answer.
duplicate_post409The same content went to this account within 10 minutes. Sends postId. Pass force: true to publish anyway.
unauthenticated401No session and no valid key.
platform_rejected_content400 · 404 · 422 · 502The channel refused the content itself. Retrying will not help. Read paths pass the upstream status through; the publish path reports it as 502.
platform_auth_error502The channel refused our access to that account. Reconnect it. Kept at 502 so it is never confused with a problem with your uplika key.
topic_tag_invalid422The topic tag has a period or an ampersand in it. Threads does not allow those.
topic_tag_not_supported422One of the channels you picked does not take a topic tag. Sends platform. Publish to it separately or drop the tag.
api_key_revoked401That key was revoked. Create a new one. Kept separate from invalid_api_key so you do not go looking for a typo.
api_key_expired401That key passed its expiry. Create a new one.
idempotency_key_reused422That Idempotency-Key was already used with a different body. Use a new key for a new request.
publish_in_flight409That post is still going out. Deleting mid-flight would let it publish after you deleted it. Wait a few seconds and try again.
thread_and_content422You passed both content and threadItems. Pass one.
thread_empty422threadItems was an empty array.
container_expired502The upload container expired before the post went out. Create the post again.
permission_denied502The channel refused for permission reasons.
internal_error500Our fault. The response carries no detail; the request id is in the logs.
rate_limited429Over the per-minute limit. Sends limit and retryAfter, and the response carries Retry-After.
account_not_found404No such connected account in this workspace.
empty_reply422The reply had no text.
media_fields_required422presign needs fileName, contentType and bytes.
not_our_post422That post was imported from the channel, not published through us.
key_name_required422Give the key a name.
invalid_locale422locale must be en or ko.
bad_post_ref422The post reference was not an id, a media id or a post link.
post_not_owned422That post belongs to an account this workspace has not connected.
post_not_in_recent422That post was not in the recent window we searched.
upload_session_not_found404That upload link is gone or expired.
upload_session_full422That upload link already has its maximum files.
media_url_invalid422Not an https URL.
media_url_blocked422That address is private, loopback or link-local.
media_url_unreachable422The host did not answer, or redirected too many times.
workspace_required400This account has more than one workspace and the request did not say which. Sends workspaces, each with id, name, slug and channels, so you can match what the person said without extra calls. Pass the x-uplika-workspace header, or the workspaceId argument on MCP tools.
workspace_mismatch404The post, channel, or media is in a different workspace on the same account. Sends workspaces. Retry there instead of connecting the account again.
youtube_title_required422A YouTube target needs options.youtube.title.
youtube_title_too_long422The title is over 100 characters. Sends limit and actual.
youtube_angle_brackets422YouTube refuses < and > in the title and the description.
youtube_video_required422YouTube takes exactly one video. Sends limit and actual.
youtube_images_not_supported422YouTube does not take images in a post. Use options.youtube.thumbnailMediaId instead. Sends actual.
youtube_tags_too_long422Tags are over 500 characters in total. A tag containing a space costs two extra for the quotes YouTube adds. Sends limit and actual.
youtube_schedule_needs_private422options.youtube.publishAt only works while privacyStatus is private.
youtube_schedule_invalid422options.youtube.publishAt was not an ISO 8601 timestamp.
youtube_thumbnail_type422A thumbnail must be image/jpeg or image/png.
youtube_thumbnail_too_large422A thumbnail can be up to 2MB. Sends limit and actual.
youtube_short_no_thumbnail422A vertical video of three minutes or less publishes as a Short, and Shorts do not take custom thumbnails. Drop the thumbnail or change the video.
youtube_no_threads422threadItems with a YouTube account would upload one video per item. Publish the video on its own.
beta_access_required403That channel is in beta and this workspace has not been approved for it yet. Request access from the dashboard. Retrying will not help.

내 키가 실제로 무엇을 보냈는지 어떻게 보나요

REST 와 MCP 호출이 요청·응답 본문과 함께 남습니다. 발행이 왜 거절됐는지 찾는 제일 빠른 길입니다. 채널이 한 말이 거기 그대로 들어 있습니다.

GET/v1/logs

최근 API·에이전트 호출. 최신 순, 본문 포함.

페이지 넘기기

이름타입설명
requestIdqueryOne call by the id an error response gave you.
outcomequeryfailed for 4xx and 5xx, ok for the rest. Omit for everything.
searchqueryMatches the path or the request id.
sincequeryYYYY-MM-DD. Calls from that day onward.
limitquery1-200, defaults to 50.
beforequeryA log id from a previous page's nextBefore.

자주 묻는 것

에러는 어떤 모양으로 오나요?

code 와 영어 message 를 가진 error 객체입니다. message 에 끼워 넣은 값은 따로 필드로도 오기 때문에, 우리 문장을 파싱하는 대신 자기 언어로 문장을 다시 만들 수 있습니다.

메시지가 왜 영어만 있나요?

응답이 언어를 타면 같은 요청이 묻는 사람에 따라 다르게 답하는 셈이라서입니다. 화면은 두 언어지만 API 는 아닙니다. message 가 아니라 code 로 그리세요.

rate_limited 를 받으면 어떻게 하나요?

기다렸다 다시 부르면 됩니다. 응답에 걸린 제한이 실려 오기 때문에 백오프를 짐작하는 대신 속도를 맞출 수 있습니다.