REST API

발행

올리고, 읽고, 다시 시도하고, 지웁니다.

GET/v1/posts

최근 발행. 최신순이고 답글은 목록에 안 섞입니다.

페이지 넘기기

이름타입설명
limitquery1-100, defaults to 20.
beforequeryA publish id from a previous page's nextBefore. Returns the ones older than it.
includeDeletedquerytrue brings back posts you deleted from the channel. They are hidden by default. Our record and its metrics stay either way.
statusqueryNarrow to one or more statuses, comma separated: scheduled, draft, publishing, published, partial, failed or cancelled. Omit for everything, scheduled and draft included.
sincequeryYYYY-MM-DD or ISO 8601. Posts dated at or after this. A scheduled post is dated by scheduledAt, a sent one by when it was created.
untilqueryYYYY-MM-DD or ISO 8601. Posts dated at or before this; a date means the whole day.
sortqueryscheduled orders by the post's date ascending and drops paging. Omit for newest first.
hasMoreresponseThe list was cut short.
nextBeforeresponsePass it back as before. Absent when there is nothing more.

POST/v1/posts

연결된 계정 하나 이상에 올립니다.

이름타입설명
content필수stringPost text. Emoji count as UTF-8 bytes.
accountIds필수string[]Ids from GET /v1/accounts.
mediaIdsstring[]Media already confirmed with complete. On Naver Blog you may leave out ids the body already points at with media:<id>; we read those from the text and attach them.
optionsobjectPer-channel settings, keyed by the channel id. The shape is in GET /v1/platforms under options and on each channel page. Some channels cannot publish without it — YouTube needs options.youtube.title and Naver Blog needs options.naver_blog.title plus a category. Every channel takes options.<channel>.content, a different text for that channel alone.
scheduledAtstringISO 8601 with a timezone offset, 10 minutes to a year out. We keep the post as scheduled and send it at that time, on every channel.
draftbooleantrue saves the post without sending. It can have no accountIds yet. Send it later with POST /v1/posts/{id}/publish.
batchIdstringOptional tag, up to 40 characters, to group posts made together (for example the same text sent to two workspaces). Comes back on every post.
replyTostringPublish this as a reply to that post id.
threadItemsobject[]Split a long post into a chain: [{ content, mediaIds? }]. The first item is the root, the rest become replies under it in order. Each item obeys the character limit on its own. Exclusive with content. Only Threads and Bluesky take a chain; the composer builds one with the item editor. If a later item fails the ones already up stay up, and the response carries thread.resumeFrom. Posts that have later items carry them back as threadItems, and PATCH takes the same field while the post is scheduled or a draft.
topicTagstringOne topic to tag the post with. Threads takes exactly one and rejects periods and ampersands. On a thread it applies to the first piece only.
Idempotency-KeyheaderSend the same key when you retry and we return the first answer instead of publishing twice. Reusing a key with a different body is a 422. Kept for 24 hours.
waitbooleanHold the response until the post is really out. Text waits up to 10 seconds, media up to 75. Past that you get the usual 202 with a next hint, and the post keeps going. Defaults to false.
curl -X POST https://api.uplika.com/v1/posts \
  -H "authorization: Bearer $UPLIKA_KEY" \
  -H "content-type: application/json" \
  -d '{
    "content": "Now with images",
    "accountIds": ["acc_01H..."],
    "mediaIds": ["med_01H..."]
  }'

GET/v1/posts/{id}

발행 하나와 대상별 상태.

대상 상태

이름타입설명
publishingstatusIn flight at the channel.
publishedstatusCarries externalId and url. If the account was disconnected afterwards, the target stays published with connectionId null and accountHandle set to that account's handle until the same account is reconnected.
partialstatusSome channels went out and some did not. Retry sends only the ones that failed. There is no 207 — publishing is 202 plus polling, so nothing is held open.
cancelledstatusDeleted from the channel. Our record and its metrics stay. Hidden from GET /v1/posts unless you pass includeDeleted=true.
failedstatusCarries error.
scheduledstatusHeld until scheduledAt. Nothing is on any channel yet. Editable.
draftstatusSaved without a time. May have no targets. Editable.

발행 하나에 채널마다 행이 하나입니다. publishing 은 진행 중, published 는 채널의 id 와 주소를 들고 있고, failed 는 사유를 들고 있습니다.

예약과 초안

scheduledAt 을 주면 글을 scheduled 로 잡고 있다가 그 시각에 우리 쪽에서 모든 채널로 보냅니다. draft: true 를 주면 누가 publish 를 부르거나 대시보드에서 띄우기 전까지 아무것도 안 나갑니다. 둘 다 status 가 scheduled 나 draft 로 오고, PATCH 로 고치고, /publish 로 앞당기고, DELETE 로 지웁니다. scheduledAt 은 시간대 오프셋이 있어야 하고 10분 뒤부터 1년까지입니다. 채널 자체 예약을 가진 채널도 있는데(options.youtube.publishAt, options.facebook.scheduledPublishTime, options.naver_blog.scheduledAt) API 로는 그대로 되지만 대시보드는 감춥니다. 한 글에 시계가 둘이면 어긋납니다.

채널마다 다른 본문

모든 채널이 options.<채널>.content 를 받습니다. 있으면 그 채널에만 공통 본문 대신 나가고, 길이·미디어 검사도 그 본문으로 합니다. 스레드 500자 글과 네이버 블로그 마크다운 본문이 한 호출에 들어가는 길입니다. 채널이 그 밖에 무엇을 받는지는 GET /v1/platforms 의 options 에 있습니다.

PATCH/v1/posts/{id}

예약·초안 글을 나가기 전에 고칩니다. 안 보낸 칸은 그대로고(네이버에만 가는 글은 mediaIds 를 빼면 새 본문의 media: 참조가 사진 목록입니다), options 는 통째로 바뀝니다. threadItems 는 체인을 통째로 바꾸고, content 만 보내면 첫 토막만 바뀝니다. 이미 나간 글의 수정은 비동기입니다. wait: true 를 주면 결과가 나올 때까지 응답을 붙잡고(최대 75초), 넘기면 202 와 함께 글을 다시 읽으라는 next 를 줍니다. 네이버 임시저장이 도중에 끊기면 네이버 임시저장함에서 찾아 있으면 회수합니다.

이름타입설명
contentstringNew post text.
accountIdsstring[]New target accounts. Replaces the current set.
mediaIdsstring[]New media, in order. Replaces the current set. On a post going only to Naver Blog, leave it out when the body places media with media: references: those references become the list and media the new body does not place is removed (the response note says how many).
optionsobjectPer-channel settings. Replaces the whole object.
scheduledAtstringNew send time, same rules as on create. null turns the post into a draft.
draftbooleantrue turns a scheduled post back into a draft.

POST/v1/posts/{id}/publish

예약·초안 글을 지금 보냅니다. 예약 크론과 같은 함수라 두 번 나가지 않습니다.

이름타입설명
waitbooleanHold the response until the post is really out, same as on create.

POST/v1/posts/{id}/retry

실패한 대상만 다시 시도합니다. 이미 나간 대상은 건너뜁니다. 네이버 임시저장 직후 끊긴 대상(naver_draft_unknown)이나 제때 확인되지 않은 네이버 답글(naver_reply_unknown)이 있으면 본문에 {"force": true} 를 실어야 다시 보냅니다. 같은 계정에 같은 글과 파일의 다른 글이 이미 있으면 그 글의 id 와 함께 409 duplicate_post 로 답하고 아무것도 다시 보내지 않습니다. {"force": true} 면 그래도 보냅니다.

DELETE/v1/posts/{id}

채널에서 글을 지우고 우리 기록도 삭제로 표시합니다.

예약·초안 글은 채널에 아무것도 없으므로 DELETE 가 우리 기록을 지웁니다. 나간 글은 채널에서 먼저 지우고 우리 기록은 지운 것으로 표시해 남깁니다.

채널에 이미 있는 것 읽기

우리 기록에는 우리를 거쳐 나간 것만 있습니다. 아래 넷은 채널 자체를 읽고, 어디에 올릴지를 고릅니다. list_channel_posts · get_quota · open_post · select_channels 뒤에 있는 라우트라 REST 로도 같은 답을 받습니다.

GET/v1/posts/{id}/thread

글 하나와 댓글·지표를 한 번에 받습니다.

GET/v1/channels/posts

앱에서 직접 쓴 글까지, 지금 채널에 있는 것.

쿼리

이름타입설명
accountIdqueryLimit to one connected account. Omit to cover every channel.
limitquery1-100, defaults to 25.

GET/v1/channels/quota

24시간 한도를 채널이 얼마나 썼다고 말하는지.

GET/v1/channels/bridge

네이버 블로그 브리지(브라우저 확장)가 켜져 있는지, 꺼졌으면 어떻게 켜는지.

GET/v1/channels/publish-options

발행 전에 채널에 물어봐야 하는 것. TikTok 전용이다. 어느 닉네임으로 나가는지, 이 계정이 지금 고를 수 있는 공개 범위, 올릴 수 있는 상태인지, 영상 길이 상한을 준다. 다른 채널은 not_supported 다.

이름타입설명
accountIdqueryRequired. These values are per connected account and change when the person edits their channel settings, so do not cache them.

POST/v1/accounts/select

연결된 채널 중 어디에 올릴지 고릅니다.

POST/v1/accounts/:id/refresh

채널 메타데이터(네이버 블로그 카테고리 등)를 다시 읽습니다. 브라우저 확장을 최대 1분 기다립니다.

이름타입설명
scopestring | string[]Omit to list candidates. "all" for every active channel, or an array mixing platform names, handles and account ids.

GET/v1/naver/blog/diagnose

네이버 블로그의 공개 RSS(최근 글, 최대 50편)를 읽어 제목이 검색을 노리고 있는지 잽니다. 검색 의도어(가격 · 방법 · 후기)가 든 제목의 비율, 날짜 · 회차로 시작하는 제목 수, 한 달 글 수, 카테고리, 제목에 반복되는 말(조사에 넣어 볼 후보이지 검색되는 키워드가 아닙니다)을 돌려줍니다. 읽은 날짜 범위를 같이 주므로 뜸한 블로그의 50편을 전부로 오해하지 않습니다. 사용자 무관 24시간 캐시이고, 캐시 미스만 사람당 하루 20회를 씁니다. 네이버 API 키가 필요 없습니다.

이름타입설명
blog필수stringA Naver blog id (2-40 letters, digits, - or _) or any link to the blog: blog.naver.com/<id>, m.blog.naver.com/<id>/<logNo>, PostView.naver?blogId=<id>.

POST/v1/naver/keywords/judge

네이버 검색 키워드를 재고 글 하나에 쓸 세트를 고릅니다. 펼치지는 않으니 갖고 있는 키워드를 보내세요. 월간검색수(검색광고)·블로그 문서수·월 발행량(API HUB)·키워드별 판정·같은 의도의 메인 1 + 서브 2~5 세트를 돌려줍니다. 결과는 사용자 무관 7일 캐시이고, 사용자당 하루 30회·300 키워드입니다.

이름타입설명
keywordsstring[]Required. 1-300 keywords. Spaces are fine; the numbers come back per keyword as you sent it.
savebooleanDefault false. true stores this research as a report (the keywords, the first set's main and subs, every set with its prompt) and returns reportId. The same person sending the same keyword set within ten minutes gets the existing reportId back with no new row and no judge call spent.
seedTextstringWhat the person typed, kept with the saved report as its label. Defaults to the first five keywords.
cacheOnlybooleanDefault false. true never calls Naver: keywords without a fresh cached value come back in unmeasured with no numbers. Use it after measuring in chunks, so the judge call is instant and spends nothing upstream.
budgetMsnumberTime budget for new measurements, default and maximum 60000. When it runs out, no new upstream call starts; the response says partial: true and lists unmeasured. Call again and the cached part is skipped.

POST/v1/naver/keywords/measure

판정·세트 없이 측정만. withDocs:false 면 문서수를 건너뜁니다.

이름타입설명
keywordsstring[]Required. 1-300 keywords. At most 40 of them are measured fresh in one call; the rest come back in unmeasured and are picked up by the next call.
withDocsbooleanDefault true. false measures search volume only.
budgetMsnumberTime budget for new measurements, default and maximum 60000. Past it the call stops starting upstream requests and returns partial: true with unmeasured.

POST/v1/naver/keywords/expand

에이전트 · API 키를 위해 네이버 자동완성으로 씨앗 키워드를 1단계 펼칩니다 (대시보드는 브라우저에서 합니다). 그 사람의 업리카 크롬 확장이 켜져 있으면 확장이 그 사람 인터넷으로 창 없이 받고, 아니면 서버가 받습니다. 찾은 단어와 어느 씨앗에서 나왔는지, 그리고 어느 길로 받았는지(via)를 돌려줍니다. 둘 다 못 하면 503 naver_autocomplete_unavailable 이고, 그때는 키워드 목록을 직접 judge 에 넘기면 됩니다.

이름타입설명
seedsstring[]Required. 1-10 seed keywords. Each is sent to Naver autocomplete once (one level); the words that come back are candidates to measure with judge, not proven keywords. Cached seven days across users. No daily quota: when the person's uplika Chrome extension answers there is no wait, and when our server answers, one call per person every 60 seconds (naver_autocomplete_busy with retryAfterSeconds).

돌려주는 것

이름타입설명
found{ keyword, from }[]Every word, the seeds first. from is seed, or L1:<seed> for a word that seed produced.
errors{ query, error }[]Seeds that could not be looked up, with why.
cachednumberHow many seeds came from the seven-day cache.
via{ cache, extension, server }How many seeds each path answered: the shared cache, the person's Chrome extension (in the background, from their own connection), or our server.

GET/v1/naver/keywords/reports

저장한 키워드 조사 기록(judge 에 save: true)을 최신순으로 줍니다. 언제, 무엇을 넣었는지, 첫 세트의 메인 · 서브, 그리고 세트마다 붙여넣을 프롬프트까지. 기록은 사람 단위라 워크스페이스를 좁힌 API 키는 403 입니다.

이름타입설명
limitnumberDefault 50, newest first.

GET/v1/naver/keywords/history

키워드 하나의 측정 시계열입니다. 실제로 잰 날마다 한 점(캐시 적중은 안 쌓입니다), 최신 먼저, 검색량 · 문서수 · 월 발행량. 빈 목록은 200 에 data: [] 입니다.

이름타입설명
keyword필수stringThe keyword, as you would send it to measure. A query parameter, since keywords are usually Korean.
limitnumberDefault 100 days, newest first.
이름타입설명
q필수stringThe search words, as a viewer would type them.
orderstringviewCount (default), date or relevance.
periodstring7d, 1m, 3m, 6m or 1y. Omit for no limit.
formatstringall (default), shorts or long. 60 seconds or less counts as a Short.
maxnumber1-50, default 25.
topicstringThe video the person wants to make, one line. Goes into prompt and is saved with the report.

GET/v1/research/youtube/reports

저장된 유튜브 검색 기록을 최신순으로 줍니다. 언제, 검색어와 필터, 주제, 영상 몇 편, 캐시였는지. 90일 보관합니다.

이름타입설명
limitnumberDefault 50, newest first.

GET/v1/research/youtube/reports/{id}

저장된 검색 하나를 그때의 영상 목록과 거기서 만든 프롬프트와 함께 줍니다.

POST/v1/naver/seo/check

발행 전에 네이버 블로그 마크다운 본문의 배치 규칙을 잽니다. 소제목 = 서브 키워드 1:1, 메인 3~5회, 태그 12~15개 전부 본문에, 첫 3단락에 상품 이름 없음, 첨부 이미지 전부 자리 잡고 캡션 10자 이상, 분량 하한. 막지는 않고 무엇이 어긋났는지와 이유를 말합니다.

이름타입설명
contentstringRequired. The Markdown body, up to 60,000 characters.
titlestringThe post title.
mainstringThe main keyword. Without it the keyword checks cannot pass.
subsstring[]Sub keywords, one per section title.
tagsstring[]The tags you will send in options.naver_blog.tags.
fixedTagsstring[]Tags that need not appear in the body (brand tags).
productsstring[]Product and brand names that must stay out of the first three paragraphs.
mediaIdsstring[]The media ids attached to the post, to check that each is placed.

POST/v1/naver/layout

발행 전에 본문이 어떻게 놓일지 봅니다. 발행이 기본으로 도는 재배치(layout: template)와 같은 계산을 아무것도 보내지 않고 돌려주고, forms: "all" 이면 프리셋 전부의 결과를 한 번에 줘서 사람이 고를 수 있습니다.

이름타입설명
content필수stringThe markdown body to lay out.
mediaIdsstring[]Attached media, in order. Images the body does not reference are placed by the form.
formstringOne preset id (default photo-story). Ignored when forms is given.
forms"all" | string[]Lay the same body out in several presets at once; the response carries layouts[] so a person can compare and pick.

GET/v1/naver/forms

기본으로 드리는 글 폼 일곱 개입니다. 폼은 글의 결입니다 — 섹션을 무엇이 여는지, 인용이 제목인지 인용문인지, 핵심 문장을 얼마나 자주 강조하는지, 섹션마다 사진이 몇 장인지, 사진에 캡션을 다는지. 전부 실제로 발행된 글에서 재서 만들었습니다. 생성할 때 id 를 주면 됩니다. 폼은 모델에게 무엇을 시킬지만 정하고 발행에는 닿지 않습니다.

GET/v1/naver/drafts

네이버 블로그 임시저장함의 초안 목록입니다. draftOnly 로 저장한 글과 네이버 에디터에서 손으로 저장한 글이 같이 나옵니다. 확장 0.7.2 부터입니다.

POST/v1/naver/drafts/{logNo}/publish

임시저장함의 초안을 네이버에 있는 그대로 발행합니다. 확장이 그 초안을 에디터에 실어 발행 버튼을 누르므로 손으로 고친 내용이 그대로 나갑니다. 응답 모양은 POST /v1/posts 와 같고, 그 초안이 업리카가 만든 것이면 그 글 기록이 발행됨으로 바뀝니다.

이름타입설명
logNo필수stringThe draft's id from GET /v1/naver/drafts.
accountIdstringRequired only when the workspace has more than one Naver Blog account.
title / categoryId / category / tags / openTypestringOverride the draft's own values. Left out, the draft goes out as it is.
waitbooleanHold until the extension finishes, up to 75 seconds like a post with media on POST /v1/posts. Past that you get the usual 202 with a next hint, and the draft keeps going out.

GET/v1/naver/grammar

네이버 블로그 본문이 알아듣는 문법입니다. 지시문·블록·인라인·속성·형광펜·미디어 참조와 상한을 담습니다. describe_grammar 도구가 돌려주는 것과 같은 안내이고 값이 한 곳에서 나오므로 에디터가 실제로 받는 것과 어긋나지 않습니다. topic 을 주면 한 절만 읽습니다.

이름타입설명
topicstringOne section instead of the whole guide. The 400 lists the ids.

POST/v1/naver/forms/extract

공개된 네이버 블로그 글을 읽어 그 결을 돌려줍니다. 짜임과 서식만 가져오고 글이나 사진은 가져오지 않습니다. 「이 글처럼 써줘」 에 씁니다. 읽은 결과를 저장해 두므로 AI 생성은 그 폼을 쓰고 네이버를 다시 부르지 않습니다. 레퍼런스가 바뀐 것을 반영하려면 다시 부르세요.

이름타입설명
urlstringRequired. A public Naver Blog post, like https://blog.naver.com/someone/224349824125. Only blog.naver.com links work, and private or deleted posts are refused. Five per minute.

GET/v1/naver/forms/snapshot

레퍼런스 글을 언제 읽었는지와 그때 블록 · 사진 수를 줍니다. 저장본만 읽고 네이버를 부르지 않습니다.

이름타입설명
urlstringRequired. The same post link you passed to extract. Answers from our database only; Naver is not called. 404 reference_not_read when it was never read.
publishing ──► published     the channel accepted it, permalink is set
           └─► partial       some channels made it, some did not
           └─► failed        none of them made it, error says why

failed     ──► publishing   retry, and only the targets that failed go again
published  ──► cancelled    you deleted it; our record and its metrics stay

자주 묻는 것

여러 채널에 한 번에 올리려면요?

accountIds 에 다 적어서 한 번만 부르면 됩니다. 요청 하나가 발행 하나를 만들고 채널마다 행이 하나씩 붙어서, 결과를 여러 호출에 걸쳐 맞춰 볼 필요가 없습니다.

202 는 무슨 뜻인가요?

글을 받았고 채널은 아직 대답하지 않았다는 뜻입니다. 무엇이 올라갔는지는 대상별 상태를 봐야 알고, 맨 위 응답은 일이 시작됐다는 것만 말합니다.

올라간 글을 지울 수 있나요?

됩니다. 채널에서 지우고 우리 기록은 삭제로 표시합니다. 그래서 그 글이 있었다는 것과 언제 나갔는지는 기록에 남습니다.