문서
에러
에러는 항상 같은 모양으로 와요. 코드는 구체적이고, 숫자 값은 따로 필드로 와요.
포샌디 API는 모든 에러를 같은 모양으로 보내요. 코드는 무엇이 잘못됐는지 구체적으로 알려 주고, 문장에 들어간 숫자 값은 따로 필드로도 보내요.
모양
실패하면 error 객체가 와요. 그 안에 code와 영어 문장 message가 있어요. message에 들어간 값(채널, 한도, 실제 값 등)은 별도 필드로도 와요. 이 값으로 한국어 안내 문장을 직접 만들 수 있어요.
{
"error": {
"code": "content_too_long",
"message": "Threads allows 500 characters. This is 620.",
"platform": "threads",
"limit": 500,
"actual": 620
}
}
위 예시는 422 응답이에요.
같은 요청이 두 번 가지 않게 막는 방법
중복 발행은 두 단계로 막아요.
- Idempotency-Key 헤더: 이 헤더를 넣으면 같은 요청을 다시 실행하지 않고 저장해 둔 응답을 그대로 돌려줘요. 성공한 결과는 24시간 보관해요. 같은 키로 다른 본문을 보내면
idempotency_key_reused로 거절해요. - 내용 비교: 같은 글과 같은 파일이 같은 계정으로 10분 안에 다시 오면
duplicate_post로 거절해요. 실패한 글을 재시도할 때도 같은 비교를 해요. 10분이 지났더라도 같은 계정에 같은 내용의 다른 글이 있으면duplicate_post로 거절해요.
일부러 같은 글을 다시 올리는 거라면 force: true를 넣어요.
위반이 여러 개면 한 번에 와요
규칙을 두 개 이상 어기면 error.errors에 전부 담겨 와요. 첫 번째 항목은 error 자리에도 그대로 들어 있어요. 그래서 한 번에 다 고칠 수 있어요. 재시도할 때마다 다음 위반을 하나씩 확인하지 않아도 돼요.
예외는 두 개예요. no_accounts와 empty_content는 다른 검사를 하기 위한 전제 조건이라 혼자 와요.
메시지는 항상 영어예요
화면은 한국어와 영어를 지원하지만, API 메시지는 영어로만 와요. 응답이 언어마다 달라지면, 같은 요청에 묻는 사람마다 다른 답이 오게 되기 때문이에요. 화면에 보여 줄 문장은 message가 아니라 code를 기준으로 만들어요.
코드
| 코드 | 상태 | 뜻과 할 일 |
|---|---|---|
verification_required | 401 | 이메일 확인을 아직 안 끝냈어요. |
next_post_taken | 409 | 이 계정에서 다음 글을 기다리는 자동화가 이미 켜져 있어요. 하나만 둘 수 있고, 다음 글이 그 자동화에 묶이면 자리가 비어요. |
past_comments_not_supported | 400 | 지난 댓글에 보내기는 Instagram과 Facebook에서만 돼요. 이 채널은 댓글을 다시 읽어 올 수 없어요. |
past_comments_no_trigger | 400 | 자동화에 댓글 트리거가 없어서 반응할 댓글이 없어요. |
automation_not_live | 409 | 자동화가 초안이에요. 먼저 켠 다음 지난 댓글에 보내요. |
past_comments_no_post | 409 | 자동화가 다음 글을 기다리는 중이라 아직 댓글을 읽을 글이 없어요. |
past_comments_running | 409 | 이미 지난 댓글에 보내는 중이에요. 끝날 때까지 기다리거나 DELETE로 멈춰요. 돌고 있는 작업은 pastComments에 있어요. |
scheduled_at_invalid | 422 | scheduledAt이 ISO 8601 형식이 아니거나 시간대 오프셋이 없어요. |
scheduled_at_range | 422 | scheduledAt이 지금부터 10분보다 가깝거나 1년보다 멀어요. |
draft_and_schedule | 422 | draft와 scheduledAt을 같이 보냈어요. 하나만 골라요. |
batch_id_invalid | 422 | batchId가 40자를 넘거나, 영문, 숫자, -, _ 말고 다른 글자가 있어요. |
naver_source_required | 409 | 원본이 네이버에만 있는 네이버 블로그 글을 update_post로 고치면서 현재 원본의 sourceVersion을 안 보냈거나, 원본을 읽은 뒤 네이버에서 글이 바뀌었어요. 옛 사본으로 만든 본문은 사진, 영상, 카드, 표, 링크, 서식을 잃어요. 응답에 source(@keep 줄이 들어간 현재 본문), keep(그 id들), sourceVersion이 와요. source를 고쳐서 sourceVersion과 함께 다시 보내요. @keep 줄은 그 블록을 지울 때만 빼요. 바뀐 것은 없어요. |
naver_keep_unknown | 422 | @keep(...) id가 고치려는 글의 블록이 아니거나, 네이버 에디터로 쓴 글에서 update_post가 아닌 곳에 @keep을 썼어요. 현재 원본의 id를 써요(naver_source_required가 알려 줘요). 바뀐 것은 없어요. |
post_not_editable | 409 | 다시 쓸 수 없는 채널에 이미 나간 글에 PATCH나 /publish를 보냈거나, 아직 안 나간 글에 재시도를 보냈어요. 메시지에 list_platforms의 채널 사유(update 기능, 채널에서 직접 쓴 글이면 update_imported)가 와요. 모든 대상이 실패한 글은 나간 적이 없다고 알려 주고 retry_post를 안내해요. 예약 글과 초안은 언제든 고칠 수 있어요. |
live_post_no_schedule | 422 | 이미 나간 글에 scheduledAt이나 draft를 넣어 PATCH했어요. 나간 글에는 예약이나 초안 상태가 없어요. content, mediaIds, options로 고쳐요. |
live_post_accounts_fixed | 422 | 이미 나간 글에 다른 accountIds를 넣어 PATCH했어요. 나간 글의 채널은 바꿀 수 없어요. 같은 id를 넣거나 빼고 보내요. |
nothing_to_change | 422 | 이미 나간 글에 content, mediaIds, options 중 아무것도 없이 PATCH했어요. 바꿀 내용이 없어요. |
target_required | 422 | 좋아요, 답글, 팔로우 요청에 대상이 없어요. postId를 넣거나 ownerHandle과 externalId를 넣어요. 팔로우는 ownerHandle을 받아요. |
thread_piece | 409 | 이어진 글(스레드)의 두 번째 이후 항목에 PATCH, /publish, DELETE를 보냈어요. 응답에 rootId가 와요. 첫 항목에 보내요. |
naver_draft_not_found | 404 | publish_naver_draft에 넣은 logNo가 블로그 임시저장함에 없어요. list_naver_drafts로 목록을 확인해요. |
account_required | 422 | 워크스페이스에 그 채널 계정이 두 개 이상 있어요. 응답에 accounts가 와요. accountId를 넣어요. |
invalid_api_key | 401 | bearer 토큰이 올바른 키가 아니에요. |
forbidden_scope | 403 | 키에 필요한 권한이 없어요. 응답에 scope가 와요. |
no_accounts | 422 | accountIds가 비어 있어요. 응답에 candidates가 오고, 연결된 채널이 없으면 next도 와요. 채널이 없는 게 아니라 만료된 경우에는 needsReconnect와 다시 연결하라는 안내가 와요. |
empty_content | 422 | 글도 파일도 없어요. |
account_not_connected | 404 | accountIds 중 하나가 연결돼 있지 않아요. 응답에 candidates가 와요. |
account_expired | 422 | 채널이 이 로그인을 끝냈어요(만료 또는 회수). 응답에 platform이 와요. POST /v1/accounts/{id}/refresh도 이 코드로 답해요. 새로고침으로는 로그인이 살아나지 않아요. Threads, Instagram, Facebook에서는 발행, 답글, 지표, 인박스, 삭제 요청도 이 코드로 답하고, 그 연결은 만료로 표시돼요. 연결 화면에서 계정을 다시 연결해요. |
platform_not_available | 422 | 아직 열리지 않은 채널이에요. 응답에 platform이 와요. |
content_too_long | 422 | 글이 채널 한도보다 길어요. 응답에 platform, limit, actual이 와요. |
media_required | 422 | 이 채널은 글만으로는 올릴 수 없어요. 응답에 platform이 와요. |
media_mixed | 422 | 지금은 발행에서 이 코드를 보내지 않아요. 사진과 영상을 섞을 수 없는 채널은 영상을 남기고 사진을 빼요. 무엇을 뺐는지는 대상의 warning에 나와요. 다른 경로로 섞인 파일이 TikTok에 닿으면 아직 이 코드로 답해요. |
media_too_many | 422 | 지금은 보내지 않아요. 채널의 사진, 영상, 캐러셀 한도를 넘으면 앞에서부터 한도만큼만 올리고 대상의 warning에 알려 줘요. 한도는 GET /v1/platforms에서 확인해요. |
media_too_large | 422 | 파일이 너무 커요. 응답에 limit, limitMb, actual이 와요. media_from_url로 가져오는 영상은 프리사인보다 한도가 낮아요. 더 큰 영상은 media_upload_link나 프리사인으로 올려요. |
media_aspect_ratio | 422 | 화면 비율이 맞지 않아요. 응답에 limit과 actual이 와요. |
media_dimensions | 422 | 영상 가로 크기가 한도보다 커요. 응답에 limit과 actual이 와요. 사진 가로 크기로는 이 코드가 오지 않아요. |
media_type_not_supported | 422 | 받지 않는 파일 형식이에요. 응답에 types가 와요. |
media_too_long | 422 | 영상 길이가 한도보다 길어요. 응답에 limit과 actual이 와요. |
media_url_invalid | 422 | https 주소가 아니에요. |
media_url_blocked | 422 | 사설망, 루프백, 링크 로컬 주소예요. |
media_url_unreachable | 422 | 서버가 답하지 않았거나, 리다이렉트가 너무 많거나, 오류로 답했어요. 서버가 짧은 오류 문장을 보내면 메시지에 같이 와요. Wikimedia 썸네일을 지원하지 않는 너비로 요청하면 그보다 작은 가장 큰 표준 너비로 한 번 더 가져와요. 몇 초짜리 Retry-After가 붙은 429는 기다렸다가 한 번 더 시도해요. 연결이 안 열리거나 응답이 없고 서버 주소가 여러 개면 다음 주소로 한 번 더 시도해요. |
media_unreachable | 503 | 채널이 그 순간 파일 주소에서 파일을 가져오지 못했어요. 포샌디가 포기하기 전에 이미 다시 시도했어요. 요청 응답이 아니라 대상의 errorCode에 남아요. 파일은 7일 보관하니 글을 재시도해요. 계속 실패하면 파일을 다시 올려요. |
workspace_required | 400 | 계정에 워크스페이스가 두 개 이상인데 요청에 어느 것인지 없어요. 응답의 workspaces에 각 워크스페이스의 id, 이름, slug, 채널이 와요. x-posendy-workspace 헤더나 MCP 도구의 workspaceId로 정해요. 헤더에 넣은 워크스페이스가 이 계정에 없거나, API 키 범위 밖이거나, 지워졌을 때도 같은 코드가 와요. 이때는 메시지가 이유를 알려 주고 workspaces에 쓸 수 있는 목록이 와요. |
workspace_mismatch | 404 | 글, 채널, 파일이 같은 계정의 다른 워크스페이스에 있어요. 응답에 workspaces가 와요. 계정을 다시 연결하지 말고 그 워크스페이스로 다시 요청해요. |
workspace_limit | 403 | 지금 플랜에 포함된 워크스페이스 수보다 이미 더 많이 갖고 있어요. 응답에 used와 limit이 와요. 워크스페이스가 요금 단위라서 재시도가 아니라 플랜을 올려야 풀려요. |
platform_already_connected | 409 | 이 워크스페이스에 그 채널이 이미 연결돼 있어요. 워크스페이스 하나에는 채널마다 하나씩만 연결돼요. 응답에 platform이 와요. 기존 연결을 끊거나 다른 워크스페이스를 써요. 같은 계정을 다시 연결하는 것은 상관없어요. |
quota_exceeded | 402 | 플랜의 사용 한도를 넘었어요. 지금은 전부 무료라 이 오류가 나지 않아요. 정식 출시 후 플랜 한도를 넘으면 이 코드가 와요. |
workspaces_invalid | 422 | 결제할 때 유료 플랜에 포함된 수보다 적은 워크스페이스를 요청했거나, 정수가 아닌 값을 보냈어요. 추가할 개수가 아니라 원하는 전체 워크스페이스 수를 보내요. |
billing_not_configured | 503 | 이 환경에서는 결제가 켜져 있지 않아요. 아직 살 수 있는 것이 없어요. |
full_access_open | 409 | 지금은 모든 기능이 모두에게 열려 있어서 살 것이 없어요. 플랜이 열리면 다시 시도해요. |
checkout_failed | 503 | 결제 대행사가 결제 화면을 돌려주지 않았어요. 결제된 것은 없어요. 다시 시도해요. |
no_subscription | 422 | 유료 구독이 없어서 결제 관리 화면을 열 수 없어요. |
portal_failed | 503 | 결제 대행사가 결제 관리 링크를 돌려주지 않았어요. 다시 시도해요. |
media_not_uploaded | 404 | media_complete를 부르지 않았어요. 파일이 없거나 비어 있으면(0바이트) media_complete도 이 코드로 422를 줘요. 기록은 남아 있으니 업로드를 끝내고 다시 불러요. |
media_expired | 404 | 업로드한 파일이 정리됐어요. 파일을 다시 올려요. 글이 나가는 순간 붙인 파일이 이미 없었을 때도 대상의 errorCode에 남아요. 이 대상은 재시도할 수 없어요. |
image_unreadable | 422 | media_complete가 사진이 덜 올라갔거나 손상된 것을 발견하고 버렸어요(업로드가 중간에 끊긴 경우). 파일을 다시 올려요. 글이 나가는 순간 사진을 읽지 못했을 때도 대상의 errorCode에 남아요. 이 대상은 재시도할 수 없어요. 파일을 다시 올리고 새 글을 만들어요. |
media_not_publishable | 422 | mediaIds에 문서(PDF, HWP, HWPX)가 들어 있어요. 문서는 DM 자동화(deliver.mediaId)에서만 받아요. 글에는 사진과 영상만 붙어요. 응답에 mediaIds가 와요. 빼고 다시 보내요. |
media_in_use | 409 | 아직 글에서 쓰는 파일이나, 다른 워크스페이스의 복사본이 함께 쓰는 원본에 DELETE를 보냈어요. 응답에 postId나 cloneId가 와요. 그쪽을 먼저 지워요. |
media_delete_failed | 503 | 저장소가 파일을 지우지 않았어요. 기록은 남아 있으니 다시 시도해요. 다른 것은 바뀌지 않았어요. |
media_store_failed | 503 | media_from_url이 파일을 가져왔지만 저장소가 받지 않았어요. 저장된 것은 없어요. 다시 시도해요. |
upload_bytes_missing | 409 | 앞서 받은 프리사인 주소로 파일이 올라오지 않아서 프리사인을 거절했어요. 응답에 stale, staleAfterMinutes, nextStep(media_upload_link)이 와요. 프리사인은 직접 파일을 PUT할 수 있을 때만 써요. 다른 사람 기기에 있는 파일은 media_upload_link, 공개 https 주소는 media_from_url을 써요. MCP로 부르면 사람에게 전할 업로드 링크(uploadLink)가 결과에 같이 오고, 링크가 살아 있는 동안 같은 링크가 와요. |
api_unreachable | 503 | MCP 도구가 포샌디 REST API를 불렀는데 JSON이 아닌 응답이 왔어요(게이트웨이 화면, 끊긴 연결 등). 읽기 요청이면 바뀐 것이 없으니 다시 시도해요. 쓰기 요청(발행, 수정, 삭제)이 시간 안에 안 끝났으면 아직 진행 중일 수 있어요. 다시 보내기 전에 메시지가 알려 주는 대로 get_post, list_posts, list_naver_drafts로 확인해요. |
naver_media_kind_mismatch | 422 | 네이버 블로그 본문에서 파일 종류와 문법이 맞지 않아요. 영상을 사진 문법으로 썼거나, 사진을 @video(media:ID)로 썼거나, @group() 안에 영상을 넣었어요. 만들 때와 고칠 때 content, 각 threadItems, options.naver_blog.content를 검사해요. 응답에 틀린 곳마다 body, line, mediaId, kind, 대신 쓸 줄이 와요. 영상은 @video(media:ID)를 한 줄에 따로 써요. 사진 문법은 네이버 블로그에 있어요. |
naver_media_over_limit | 422 | 네이버 블로그 본문에 넣은 사진이나 영상이 네이버의 글당 한도보다 많아요. 본문만으로 넘거나, 앞에 붙는 첨부 파일과 합쳐서 넘는 경우예요. 저장 전에 검사해요. 응답에 referenced, limits, droppedIds, total, unplaced가 와요. 바뀐 것은 없어요. 본문에 안 쓰는 첨부 파일을 빼거나, 본문에 넣는 파일을 줄이거나, mediaIds에 본문에 쓰는 파일만 넣어요. |
naver_media_ref_unattached | 422 | 네이버 블로그 본문이 글에 첨부되지 않은 파일을 가리켜요. 이대로면 확장 프로그램이 그 줄에서 멈춰요. 서버는 파일 참조로 안 읽는데 확장은 읽는 모양으로 사진 줄을 쓴 경우에 생겨요(예: 줄 끝에 br 태그를 붙인 경우). 응답에 mediaIds가 와요. 바뀐 것은 없어요. 그 id를 mediaIds에 넣거나, 사진을 한 줄에 하나씩 쓰고 중괄호 속성은 한 쌍만, 그 뒤에는 아무것도 쓰지 않아요. |
media_ref_unknown | 400 | 네이버 블로그 본문의 media: 참조 id를 찾을 수 없어요. 응답에 그 id가 와요. media_presign이나 media_from_url이 돌려준 값과 비교해요. 올린 파일은 글이 나가고 하루쯤 뒤에 정리돼요. 이미 나간 네이버 글의 사진은 현재 원본의 @keep 줄이니 그 줄은 그대로 두고, media:는 지금 올리는 사진에만 써요. 같은 내용으로 재시도해도 소용없어요. |
media_ref_not_supported | 422 | 본문에 media: 참조를 썼는데, 이 문법은 네이버 블로그와 Ghost만 알아들어요. 다른 채널에서는 글자 그대로 올라가요. 그 채널은 따로 올리거나, 참조를 빼고 mediaIds를 써요. |
media_ref_mixed_channels | 422 | 본문에 media: 참조를 썼는데 글이 네이버 블로그와 Ghost에 같이 가요. 두 채널은 참조를 읽는 규칙이 달라요. 채널마다 글을 따로 올려요. |
ghost_title_required | 422 | Ghost(준비 중)에는 options.ghost.title이 필요해요. |
ghost_title_too_long | 422 | Ghost 제목이 코드 포인트 기준 255자를 넘어요. 응답에 limit과 actual이 와요. |
ghost_tag_invalid | 422 | Ghost 태그가 비었거나, 191자를 넘거나, 쉼표가 있거나, #로 시작해요(#로 시작하면 Ghost가 숨김 태그로 만들어요). 응답에 limit이 와요. |
ghost_directive_not_supported | 422 | Ghost 본문에 네이버 블로그 문법(@group, @video, @card, 사진 뒤 중괄호)을 썼어요. Ghost에서는 글자 그대로 나와요. 응답에 line이 와요. |
media_not_configured | 503 | 파일 저장소가 설정돼 있지 않아요. |
post_not_found | 404 | 이 워크스페이스에 그런 글이 없어요. 계정 연결을 끊을 때 발행 기록이 지워진 글에 DELETE를 보내도 이 코드가 와요. 이때는 글이 채널에 아직 있으니 채널 링크로 delete_post를 부르라고 메시지가 알려 줘요. |
media_not_found | 404 | 이 워크스페이스에 그런 파일이 없어요. |
key_not_found | 404 | 그런 API 키가 없어요. |
grant_not_found | 404 | 그런 승인 기록이 없어요. |
nothing_to_retry | 422 | 모든 대상이 이미 성공했거나, 계정 연결을 끊으면서 발행 기록이 지워져 남은 대상이 없어요. 후자면 메시지가 채널 링크를 알려 줘요. |
not_published | 422 | 글이 아직 나가지 않았어요. |
delete_window_expired | 422 | Telegram은 봇이 올린 메시지를 48시간 안에만 지울 수 있어요. 재시도해도 소용없어요. 앱에서 직접 지워요. |
platform_error | 503 | 채널이 거절했거나, 답하지 않았거나, 업로드 세션을 끝내기 전에 끊었어요. |
duplicate_post | 409 | 같은 내용이 10분 안에 이 계정으로 나갔어요. 응답에 postId가 와요. 그래도 올리려면 force: true를 넣어요. 재시도에서도 이 코드가 와요: 같은 내용과 파일의 다른 글이 같은 계정에서 대기, 발행 중, 발행됨, 예약 상태이고, 재시도하는 글보다 10분 넘게 먼저 만들어진 글이 아니면 실패한 시점과 상관없이 막아요. 이때 postId는 그 다른 글이고, targets는 보류된 이 글의 실패 대상이에요. 재시도된 것은 없어요. 그래도 보내려면 force: true를 넣어요. |
unauthenticated | 401 | 로그인 세션도 올바른 키도 없어요. |
platform_rejected_content | 400, 404, 422, 503 | 채널이 내용 자체를 거절했어요. 재시도해도 소용없어요. 읽기 요청은 채널의 상태 코드를 그대로 전하고, 발행은 503으로 알려요. |
platform_auth_error | 503 | 채널이 그 계정에 대한 포샌디의 접근을 거절했어요. 계정을 다시 연결해요. 포샌디 API 키 문제와 헷갈리지 않도록 503으로 보내요. |
topic_tag_invalid | 422 | 주제 태그에 마침표나 &가 있거나, UTF-8 기준 50바이트(한글 약 16자)를 넘어요. limit과 actual에 숫자가 와요. Threads가 받지 않는 형식이에요. |
youtube_video_not_replaceable | 대상 기록 | 이미 올라간 YouTube 영상을 고치면서 새 영상 파일을 보냈어요. YouTube는 올라간 영상의 파일을 바꾸지 않아요. 제목, 설명, 태그, 공개 범위만 고치거나 새 영상으로 올려요. |
topic_tag_not_supported | 422 | 고른 채널 중 하나가 주제 태그를 받지 않아요. 응답에 platform이 와요. 그 채널은 따로 올리거나 태그를 빼요. |
api_key_revoked | 401 | 폐기된 키예요. 새 키를 만들어요. 오타를 찾느라 시간 쓰지 않도록 invalid_api_key와 나눠 보내요. |
api_key_expired | 401 | 만료일이 지난 키예요. 새 키를 만들어요. |
idempotency_key_reused | 422 | 그 Idempotency-Key를 이미 다른 본문으로 썼어요. 새 요청에는 새 키를 써요. |
publish_in_flight | 409 | 글이 아직 나가는 중이에요. 이때 지우면 지운 뒤에 글이 올라갈 수 있어요. 몇 초 기다렸다가 다시 시도해요. |
extension_outdated | 422 | 사용자 브라우저의 포샌디 확장 프로그램이 이 작업을 하기에 옛 판이에요. 보낸 것은 없어요. 응답에 extensionVersion과 requiredVersion이 와요. 포샌디가 그 브라우저에 확장 업데이트를 요청하고, 네이버 작업이 없으면 확장이 스스로 바뀌어요. 1~2분 뒤 다시 불러요. 그래도 옛 판이면 대시보드 위쪽 확장 배지에서 「지금 업데이트」를 눌러 달라고 사용자에게 안내해요. 필요한 판이 아직 스토어 심사 중이면 메시지가 알려 주고 bridge.awaitingRelease가 true예요. 이때는 업데이트해도 찾는 판이 없고, 게시되면 브라우저가 알아서 설치해요. 그 전까지는 네이버에서 직접 해야 해요. |
thread_and_content | 422 | content와 threadItems를 같이 보냈어요. 하나만 보내요. |
thread_empty | 422 | threadItems가 빈 배열이에요. |
container_expired | 503 | 글이 나가기 전에 업로드 컨테이너가 만료됐어요. 글을 다시 만들어요. |
permission_denied | 503 | 채널이 권한 문제로 거절했어요. |
invalid_json | 400 | 요청 본문을 JSON으로 읽지 못했어요. 문법이 깨졌거나, 맨 위가 객체나 배열이 아니거나, 문자셋이 UTF-8이 아닌 경우예요. |
코드별로 더 자세한 해결 방법은 안 될 때에 있어요.
