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_required | 401 | Email and phone verification are not finished. |
invalid_api_key | 401 | The bearer token is not a valid key. |
forbidden_scope | 403 | The key lacks the scope. Sends scope. |
no_accounts | 422 | accountIds 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_content | 422 | Neither text nor media was given. |
account_not_connected | 404 | One of the accountIds is not connected. Sends candidates. |
account_expired | 422 | The channel token expired. Sends platform. |
platform_not_available | 422 | That channel is not live yet. Sends platform. |
content_too_long | 422 | Sends platform, limit and actual. |
media_required | 422 | That channel cannot post text alone. Sends platform. |
media_mixed | 422 | Images and video in one post. Sends platform. |
media_too_many | 422 | Sends platform, limit and actual. |
media_too_large | 422 | Sends limit, limitMb and actual. |
media_aspect_ratio | 422 | Sends limit and actual. |
media_dimensions | 422 | Video wider than the limit. Sends limit and actual. Image width is never a reason. |
media_type_not_supported | 422 | Sends types. |
media_too_long | 422 | Video longer than the limit. Sends limit and actual. |
media_url_invalid | 422 | Not an https URL. |
media_url_blocked | 422 | That address is private, loopback or link-local. |
media_url_unreachable | 422 | The host did not answer, or redirected too many times. |
workspace_required | 400 | This 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_mismatch | 404 | The 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_uploaded | 404 | media_complete was never called. |
media_expired | 404 | The upload was reclaimed. Upload the file again. |
media_not_configured | 503 | Media storage is not set up. |
post_not_found | 404 | No such publish in this workspace. |
media_not_found | 404 | No such media in this workspace. |
key_not_found | 404 | No such API key. |
grant_not_found | 404 | No such authorization. |
nothing_to_retry | 422 | Every target already succeeded. |
not_published | 422 | The post has not gone out yet. |
platform_error | 502 | The channel refused or did not answer. |
duplicate_post | 409 | The same content went to this account within 10 minutes. Sends postId. Pass force: true to publish anyway. |
unauthenticated | 401 | No session and no valid key. |
platform_rejected_content | 400 · 404 · 422 · 502 | The 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_error | 502 | The 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_invalid | 422 | The topic tag has a period or an ampersand in it. Threads does not allow those. |
topic_tag_not_supported | 422 | One of the channels you picked does not take a topic tag. Sends platform. Publish to it separately or drop the tag. |
api_key_revoked | 401 | That key was revoked. Create a new one. Kept separate from invalid_api_key so you do not go looking for a typo. |
api_key_expired | 401 | That key passed its expiry. Create a new one. |
idempotency_key_reused | 422 | That Idempotency-Key was already used with a different body. Use a new key for a new request. |
publish_in_flight | 409 | That 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_content | 422 | You passed both content and threadItems. Pass one. |
thread_empty | 422 | threadItems was an empty array. |
container_expired | 502 | The upload container expired before the post went out. Create the post again. |
permission_denied | 502 | The channel refused for permission reasons. |
internal_error | 500 | Our fault. The response carries no detail; the request id is in the logs. |
rate_limited | 429 | Over the per-minute limit. Sends limit and retryAfter, and the response carries Retry-After. |
account_not_found | 404 | No such connected account in this workspace. |
empty_reply | 422 | The reply had no text. |
media_fields_required | 422 | presign needs fileName, contentType and bytes. |
not_our_post | 422 | That post was imported from the channel, not published through us. |
key_name_required | 422 | Give the key a name. |
invalid_locale | 422 | locale must be en or ko. |
bad_post_ref | 422 | The post reference was not an id, a media id or a post link. |
post_not_owned | 422 | That post belongs to an account this workspace has not connected. |
post_not_in_recent | 422 | That post was not in the recent window we searched. |
upload_session_not_found | 404 | That upload link is gone or expired. |
upload_session_full | 422 | That upload link already has its maximum files. |
media_url_invalid | 422 | Not an https URL. |
media_url_blocked | 422 | That address is private, loopback or link-local. |
media_url_unreachable | 422 | The host did not answer, or redirected too many times. |
workspace_required | 400 | This 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_mismatch | 404 | The 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_required | 422 | A YouTube target needs options.youtube.title. |
youtube_title_too_long | 422 | The title is over 100 characters. Sends limit and actual. |
youtube_angle_brackets | 422 | YouTube refuses < and > in the title and the description. |
youtube_video_required | 422 | YouTube takes exactly one video. Sends limit and actual. |
youtube_images_not_supported | 422 | YouTube does not take images in a post. Use options.youtube.thumbnailMediaId instead. Sends actual. |
youtube_tags_too_long | 422 | Tags 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_private | 422 | options.youtube.publishAt only works while privacyStatus is private. |
youtube_schedule_invalid | 422 | options.youtube.publishAt was not an ISO 8601 timestamp. |
youtube_thumbnail_type | 422 | A thumbnail must be image/jpeg or image/png. |
youtube_thumbnail_too_large | 422 | A thumbnail can be up to 2MB. Sends limit and actual. |
youtube_short_no_thumbnail | 422 | A 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_threads | 422 | threadItems with a YouTube account would upload one video per item. Publish the video on its own. |
beta_access_required | 403 | That 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·에이전트 호출. 최신 순, 본문 포함.
페이지 넘기기
| 이름 | 타입 | 설명 |
|---|---|---|
requestId | query | One call by the id an error response gave you. |
outcome | query | failed for 4xx and 5xx, ok for the rest. Omit for everything. |
search | query | Matches the path or the request id. |
since | query | YYYY-MM-DD. Calls from that day onward. |
limit | query | 1-200, defaults to 50. |
before | query | A log id from a previous page's nextBefore. |
자주 묻는 것
에러는 어떤 모양으로 오나요?
code 와 영어 message 를 가진 error 객체입니다. message 에 끼워 넣은 값은 따로 필드로도 오기 때문에, 우리 문장을 파싱하는 대신 자기 언어로 문장을 다시 만들 수 있습니다.
메시지가 왜 영어만 있나요?
응답이 언어를 타면 같은 요청이 묻는 사람에 따라 다르게 답하는 셈이라서입니다. 화면은 두 언어지만 API 는 아닙니다. message 가 아니라 code 로 그리세요.
rate_limited 를 받으면 어떻게 하나요?
기다렸다 다시 부르면 됩니다. 응답에 걸린 제한이 실려 오기 때문에 백오프를 짐작하는 대신 속도를 맞출 수 있습니다.