> ## Documentation Index
> Fetch the complete documentation index at: https://docurise.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# 문서 생성

> 노션 페이지, 포스트(서식 있는 텍스트 또는 HTML 페이지), 리드 수집 폼으로 DocuRise 문서를 생성합니다



## OpenAPI

````yaml api-reference/openapi.ko.yaml POST /api/preview/documents
openapi: 3.1.0
info:
  title: DocuRise 퍼블릭 API
  version: preview
  description: >
    DocuRise 프로그래매틱 접근입니다.


    **이 API는 프리뷰 단계입니다.** 모든 경로는 `/api/preview/` 하위에 버전이 지정되어 있으며,

    프리뷰 단계에서는 호환성을 보장하지 않습니다 — 필드, 값, 에러 코드가 추가, 변경 또는 제거될

    수 있습니다. 계약이 안정화되면 `/api/v1/`로 승격됩니다.

    변경 사항은 [변경 이력](/ko/changelog)에서 확인하세요.


    ### 접근 조건


    모든 라우트는 다음을 요구합니다:

    1. 유효한 API 키 (아래 인증 참고).

    2. 호출하는 계정의 요금제가 **Iconic 이상**이어야 합니다.

    3. 프리뷰 기간 동안 API는 일부 계정에서만 사용이 가능하며, 요금제와는 별개입니다.
       사용할 수 없는 계정은 아래 모든 라우트에서 `404`를 받습니다. 사용할 수 있어야
       하는 계정이라면 DocuRise 담당자에게 문의하세요.

    키 발급 및 관리는 대시보드의 **Settings → Developers**에서 할 수 있습니다.


    ### 멱등성(Idempotency)


    리소스를 생성하는 `POST` 엔드포인트는 `Idempotency-Key` 헤더가 필요합니다. 같은 키와 같은

    요청 본문으로 재요청하면 두 번째 리소스를 만드는 대신 원래 응답(동일한 상태 코드, 동일한

    본문)을 그대로 반환합니다 — 타임아웃이나 연결 끊김 시 안전하게 재시도할 수 있습니다. 키는

    7일 동안 기억되며, 그 이후에 같은 키로 요청하면 새 요청으로 처리됩니다. 같은

    키를 **다른** 본문으로 재사용하면 `IDEMPOTENCY_KEY_CONFLICT`로 거부됩니다. 매 논리적 작업마다

    새로운 키(UUID나 ULID면 충분)를 생성하세요 — HTTP 시도마다 새로 생성하지 마세요.


    예외: `POST /api/preview/tags`는 `Idempotency-Key`를 받지 않습니다. 같은 이름의 태그를 두 번

    만들어도 아무것도 바뀌지 않으므로(두 번째 호출은 기존 태그를 `created: false`로 반환), 재시도는

    항상 안전합니다.


    또 다른 예외: `POST /api/preview/segments/preview`는 `Idempotency-Key`를 받지 않습니다.

    아무것도 저장하지 않으므로 반복 호출해도 항상 안전합니다.


    ### 응답 호환성


    응답은 시간이 지나면서 필드가 추가될 수 있고, `type`/`code` 형태의 열거값(예를 들어 향후

    추가될 `LeadFormQuestion.type` 값이나 새로운 에러 `code`)도 새 값이 늘어날 수 있습니다.

    알지 못하는 필드나 열거값은 항상 무시하고 에러로 취급하지 마세요 — 이것이 `preview` 기간

    동안의 모든 추가적 변경을 하위 호환되게 만드는 원칙입니다.


    응답에서 필드가 아예 없는 것과 값이 `null`로 존재하는 것은 항상 같은 의미가 아닙니다. 타입에

    `null`이 전혀 포함되지 않는 필드는 해당하지 않을 때 아예 생략됩니다 — `null`로 전송되는 일은

    없습니다(예를 들어 `retryAfterSeconds`는 백오프 힌트가 없을 때 완전히 생략되며, `null`로

    설정되지 않습니다). 타입에 `null`을 명시적으로 허용하는 필드는 "값이 없음"과는 구분되는 실제

    의미로 `null`을 사용합니다(예를 들어 문서의 `expiresAt: null`은 "알 수 없음"이나 "해당

    없음"이 아니라, 고정 만료일이 없다는 뜻입니다).


    모든 필드명은 `notionUrl`, `leadFormQuestions`, `collectName` 등과 같이 모든 요청/응답 본문에서

    일관되게 `camelCase`를 사용합니다.


    ### 식별자


    `id` 값은 불투명한(opaque) 문자열입니다. 구조화되지 않은 토큰으로 저장하고 비교하세요 —

    파싱하거나 형식을 검증하거나 길이·문자 구성에 대해 어떤 가정도 하지 마세요.


    ### 에러


    모든 에러 응답은 동일한 형식을 갖습니다:


    ```json

    { "error": { "code": "STRING", "message": "human-readable", "retryable":
    false } }

    ```


    `message`가 아니라 `code`를 기준으로 분기하세요 — `message`는 로그/디버깅용 자유 텍스트이며

    예고 없이 문구가 바뀔 수 있습니다. `code`가 안정적인 계약입니다. `retryable`은 동일한 요청을

    그대로 재시도해도 되는지를 나타내며, 존재할 경우 `retryAfterSeconds`가 구체적인 백오프

    힌트를 제공합니다.


    요청 본문에 NUL 문자(`\u0000`)가 문자열 값이나 속성 이름에 들어 있으면 아무것도 저장하기 전에

    `400 VALIDATION_ERROR`로 거부됩니다.


    ### 속도 제한


    계정이 보유한 키 개수와 무관하게, 모든 `/api/preview/*` 라우트에 대해 계정당 분당 60회

    요청으로 제한됩니다. 초과 시 `Retry-After` 헤더와 함께 `429`가 반환됩니다.


    이와 별도로, 계정이 보유한 키 개수와 무관하게 이 라우트들에서 한 계정이 동시에 실행 중인

    요청은 최대 10개입니다. 11번째가 될 요청은 `code: TOO_MANY_CONCURRENT_REQUESTS`와

    `Retry-After: 1`이 담긴 `429`를 반환합니다. 실행 중인 요청 하나가 끝날 때까지 기다린 뒤

    재시도하세요. 이 제한은 아직 끝나지 않은 요청을 세므로, 분당 60회 이내여도 적용될 수 있습니다.
  contact:
    name: DocuRise
    url: https://docurise.io
servers:
  - url: https://docurise.io
    description: 프로덕션
security:
  - apiKey: []
tags:
  - name: Documents
    description: >-
      문서(노션 페이지, 포스트, 리드 수집 폼)를 생성하고, 내가 소유한 문서를 목록 조회하거나 읽고, 초안을 게시하며, 상태, 만료일,
      이메일 보호 설정을 변경합니다.
  - name: Analytics
    description: >-
      내 문서 하나의 조회, 방문자, 리드, 전환 집계 수치를 조회하고, 유입 채널별 분포, 이전 기간과의 비교, 폼 퍼널로도 확인하며,
      내 모든 문서를 조회, 방문자, 리드 기준으로 순위를 매겨 조회합니다.
  - name: Folders
    description: 폴더와 강의를 항목 수와 함께 조회합니다.
  - name: Products
    description: 상품 카탈로그를 조회합니다(읽기 전용).
  - name: Audiences
    description: >-
      태그와 그룹을 연락처 수와 함께 조회하고, 규칙으로 그룹을 만들고, 규칙에 맞는 연락처 수를 미리 세고, 그룹 하나를 규칙과 함께
      조회합니다. 수치만 제공하며 연락처 정보는 절대 반환하지 않습니다.
  - name: Contacts
    description: 내 연락처 하나에 태그를 추가하거나 제거합니다. 연락처 정보는 반환하지 않습니다.
  - name: Email
    description: 이메일 1회성 발송, 1회성 발송별 집계 수치, 자동화를 조회합니다. 이메일만 해당하며 카카오는 포함되지 않습니다.
  - name: Instagram
    description: Instagram 댓글-DM 자동화 규칙을 생성, 조회, 수정, 일시정지, 재개합니다.
  - name: Account
    description: 호출에 사용 중인 API 키를 확인합니다.
paths:
  /api/preview/documents:
    post:
      tags:
        - Documents
      summary: 문서 생성하기
      description: >
        세 가지 종류 중 하나의 새 DocuRise 문서를 생성하고 공개 URL을 반환합니다. 제시된 API 키에

        `documents:write` 범위가 필요합니다.


        필수 필드 `kind`가 문서 종류를 정하며, 어떤 필드를 쓸 수 있는지도 함께 정해집니다. 다른

        종류에 속한 필드를 보내면 `400 VALIDATION_ERROR`이며, `kind`가 없거나 알 수 없는 값인 경우도

        마찬가지입니다(`message`에 `kind`가 표시됩니다).


        | `kind` | 생성되는 것 | 필드 |

        |---|---|---|

        | `notion` | 공개 노션 페이지를 기반으로 한 문서 | `title`, `description`, `notionUrl`,
        `status` |

        | `post` | 크리에이터가 작성한 포스트 | `title`, `description`, `content` 또는 `html`
        중 정확히 하나, `status` |

        | `form` | 독립형 리드 수집 폼 | `title`, `description`, `status`,
        `collectName`, `collectCompany`, `collectPhone`, `leadFormQuestions` |


        텍스트 필드(`title`, `description`, 게시물의 `content` 또는 `html`, 리드 폼 질문의
        `label`·`description`·`choices`)에는 NUL(U+0000)

        문자를 넣을 수 없습니다. 넣으면 필드 이름이 표시된 `400 VALIDATION_ERROR`이며 아무것도 생성되지

        않습니다. 그 밖의 문자는 모두 허용됩니다.


        **`notion`.** 노션 페이지는 반드시 웹에 게시(공개 공유)되어 있어야 합니다. DocuRise는

        계정별 노션 OAuth를 사용하지 않으며 비공개 페이지는 읽을 수 없습니다. 응답이 반환되기

        전에 노션 페이지를 가져와 검증하므로, `201`이 반환되면 `url`의 문서가 즉시 사용 가능한

        상태입니다.


        **`post`.** 크리에이터가 작성한 포스트입니다. 본문은 두 필드 중 정확히

        하나로 보냅니다: `content`(서식 있는 텍스트 HTML) 또는 `html`(HTML 페이지, 아래 "HTML

        포스트" 참고). 둘 다 보내거나 둘 다 보내지 않으면 `400 VALIDATION_ERROR`이며 아무것도

        생성되지 않습니다.


        YouTube나 Vimeo 동영상은 `content`에 자리표시자로 임베드합니다:

        `<div data-video-embed="youtube:VIDEO_ID"></div>`(11자리 동영상 ID),

        `<div data-video-embed="vimeo:VIDEO_ID"></div>`(숫자) 또는 비공개(unlisted)
        Vimeo

        동영상이면 `<div data-video-embed="vimeo:VIDEO_ID:HASH"></div>`(해시는 소문자 16진수

        6~20자)입니다. 그 밖의 값은 콘텐츠를 정제할 때 제거되며 `<iframe>`도 마찬가지이므로,

        붙여넣은 임베드 코드나 URL만으로는 임베드가 만들어지지 않습니다. 임베드만 있는 포스트도

        콘텐츠가 있는 것으로 봅니다.


        본문은 어느 필드로 보냈든 정제한 뒤에 검사합니다. **업로드한** 동영상의

        자리표시자(`data-video-id`)가 포함된 본문은 `422 VIDEO_CONTENT_UNSUPPORTED`로

        거부됩니다. API로 만든 포스트에는 업로드된 동영상을 붙일 초안이 없기 때문입니다. 기본값인

        `status: live`로 만드는 포스트는 실제 콘텐츠가 있어야 합니다 — 정제 후 내용이 비어 있으면

        `422 EMPTY_BODY`, `{recipient_name}`과 같은 이메일 템플릿 변수가 본문에 남아 있으면

        `422 UNSUPPORTED_VARIABLE`로 거부됩니다. `PATCH /api/preview/documents/{id}`로
        초안을

        게시할 때 적용하는 것과 동일한 검증이며, `status: draft`로 만들면 두 검증 모두 적용되지

        않습니다(그 게시 검증과 동일).


        **HTML 포스트(`html`).** `html`은 페이지 전체를 하나의 HTML 조각으로 담습니다. 자체 `<style>`

        블록, 레이아웃, 색상을 쓸 수 있고 최대 200,000자입니다(넘으면 `html`이 표시된

        `400 VALIDATION_ERROR`). 서식 있는 텍스트로는 표현할 수 없는

        페이지에 사용하세요. HTML 포스트가 활성화된 계정에서만 쓸 수 있으며, 그렇지 않은 계정의 요청은

        `403 HTML_MODE_DISABLED`이고 아무것도 생성되지 않습니다(대신 `content`로 포스트를

        만드세요). 위의 `status` 규칙은 그대로 적용되고, 응답도 같은 `{ id, url }`입니다.


        **`status: live`로 만든 HTML 포스트는 사람의 검토 없이 즉시 게시됩니다.** 사람이 먼저 페이지를

        확인하게 하려면 `status: draft`로 만든 뒤 대시보드에서(또는

        `PATCH /api/preview/documents/{id}`에 `status: live`를 보내) 게시하세요.


        서버는 HTML을 저장하거나 표시하기 전에 정제(sanitize)하며, 응답에는 무엇이 제거되었는지

        나타나지 않습니다. 유지되는 항목에 맞춰 작성하세요.


        - **태그.** 서식 있는 텍스트 `content`의 태그에 더해 `header`, `nav`, `main`,
        `section`,
          `article`, `aside`, `footer`, `figure`, `figcaption`, `address`, `small`, `style`을 쓸 수
          있습니다. 그 밖의 태그는 제거되며, 특히 `script`, `iframe`, `object`, `embed`, `link`,
          `meta`, `base`, `template`, `svg`, `canvas`, `video`, `audio`, `picture`와 모든 폼 요소
          (`form`, `input`, `button`, `select`, `textarea`)가 그렇습니다. 스크립트와 이벤트 핸들러는
          실행되지 않으므로, 상호작용은 CSS(`:hover`, `:target`, 애니메이션)로 구현하고 `<button>`
          대신 버튼처럼 꾸민 링크를 쓰세요.
        - **속성.** 정해진 허용 목록만 유지됩니다(`class`, `id`, `style`, `href`, `src`, `alt`,
          `width`, `height`, `aria-*` 포함). 나머지는 모든 `on*` 핸들러와, 에디터가 직접 쓰는 표식(`data-video-id`, `data-video-embed`,
          `data-subscribe-button`)을 뺀 모든 `data-*`를 포함해 제거됩니다. 이 표식은 값이 올바를 때만
          유지됩니다. `data-video-id` 표식은 거부되고(`VIDEO_CONTENT_UNSUPPORTED`),
          `data-subscribe-button`은 본인 소유의 구독 폼에 대해서만, 포스트당 서로 다른 폼 최대 20개까지
          유지됩니다.
          링크의 `href`는 `https://`, `http://`, `mailto:`, `tel:`, `#앵커`, `/경로` 중 하나여야 합니다.
        - **이미지.** `src`는 절대 `https://` URL이거나 `data:image`(PNG, JPEG, GIF,
        WebP)여야
          합니다. `images/a.png` 같은 상대 경로는 제거되어 이미지가 표시되지 않습니다. 원격 이미지는
          작성한 그대로 유지되며 DocuRise로 복사되지 않으므로, 계속 공개되는 URL(만료되는 링크가
          아닌 것)을 쓰세요. `data:` 이미지도 200,000자 한도에 포함됩니다.
        - **CSS.** `<style>` 블록과 인라인 `style` 속성은 필터를 거친 뒤 유지됩니다. `url()`에는 절대
          `https://` URL 또는 `data:image`(PNG, JPEG, GIF, WebP)만 쓸 수 있고, `@import`,
          `@font-face`, `@container`, `@page`, CSS 중첩, `attr()`은 제거됩니다. 사용자 정의 속성
          (`--name`)은 유지되지만 DocuRise의 자체 속성과 충돌하지 않도록 이름이 바뀝니다. 페이지의
          루트 요소에서는 `position`, `top`/`right`/`bottom`/`left`/`inset`, `z-index`, `transform`,
          `filter`, `overflow`, `contain`, `clip-path`, `mask`, `float`가 무시됩니다. 백슬래시가
          하나라도 있거나, 문법 오류가 있거나, 중첩이 8단계를 넘는 스타일시트는 거부됩니다.
          `422 HTML_REJECTED`를 참고하세요.
        - **글꼴.** `font-family`(또는 `font` 단축 속성)에 아래 이름을 그대로 쓰세요:
          `"Pretendard"`, `"Paperlogy"`, `"Noto Serif KR"`. 굵기는 보통(400)과 굵게(700)만
          제공됩니다. 이 글꼴은 DocuRise가 직접 제공합니다. 이름 뒤에는 항상 대체 글꼴 목록을
          붙이세요. 예: `"Pretendard","Apple SD Gothic Neo","Noto Sans KR",system-ui,sans-serif`.
          그 밖의 글꼴 이름은 읽는 사람 기기의 글꼴로 표시되며, 글꼴 파일, `@font-face`,
          Google Fonts 링크는 아무 효과가 없습니다.
        - **표시.** 페이지는 독립 문서가 아니라 포스트 본문 안에 표시됩니다. 따라서 `<!DOCTYPE>`,
          `<html>`, `<head>`, `<body>` 없이 하나의 조각으로 보내고, 루트 요소는 하나로 하며, 그
          루트에 배경과 글꼴을 직접 지정하세요(`body`, `html`, `:root` 선택자는 그 루트에
          적용됩니다). 다크 모드에서도 항상 밝은 배경 위에 표시됩니다. 너비 기반 미디어 쿼리는 화면이
          아니라 페이지가 표시되는 영역의 너비를 기준으로 합니다.

        **`form`.** 노션, PDF, 포스트 콘텐츠가 없는 독립형 리드 수집 페이지입니다. 폼은 항상 방문자의 이메일을 요구하며,
        비밀번호,

        허용 목록, 팝업 CTA가 없습니다. `leadFormQuestions`의 각 항목은 `LeadFormQuestion` 스키마에
        정의된

        키만 받으며, 알 수 없는 키는 조용히 버려지지 않고 `400 VALIDATION_ERROR`로 거부됩니다.


        **상태.** 선택 필드 `status`는 `PATCH /api/preview/documents/{id}`와 같은 값을 쓰며
        기본값은

        `live`입니다. 생략하면 게시된 문서가 만들어집니다. 종류별로 생성할 수 있는 값은 다음과

        같습니다.


        | `kind` | `live` | `draft` | `inactive` |

        |---|---|---|---|

        | `notion` | 생성(기본값) | `422 STATUS_NOT_SUPPORTED` | `422
        STATUS_NOT_SUPPORTED` |

        | `post`, `form` | 생성(기본값) | 초안으로 생성 | `422 STATUS_NOT_SUPPORTED` |


        `status: draft`로 만든 `post`나 `form`은 게시되지 않으며 게시된 문서 한도에 포함되지 않습니다. 초안은
        `PATCH /api/preview/documents/{id}`에 `status: live`를 보내거나 대시보드에서 게시할 수
        있습니다. 이미 꺼진 상태의 문서를 만드는 것

        (`status: inactive`)은 아직 어떤 종류에서도 지원되지 않습니다. `status: live`로 만든 뒤

        `PATCH`로 `inactive`로 바꾸세요. `live | draft | inactive` 밖의 값은 `status`를 알려
        주는

        `400 VALIDATION_ERROR`입니다.


        **멱등성.** 세 종류 모두 하나의 `Idempotency-Key` 흐름을 사용합니다. 같은 키는 같은 요청만

        재생합니다. 다른 `kind`(또는 그 밖의 다른 본문)로 같은 키를 다시 사용하면

        `409 IDEMPOTENCY_KEY_CONFLICT`입니다.


        문서는 동기적으로 생성되므로, `201`이 반환되면 문서가 즉시 존재합니다.
      operationId: createDocument
      parameters:
        - name: Idempotency-Key
          in: header
          required: true
          description: 이 생성 시도를 식별하는 클라이언트 생성 고유 문자열(255자 이하)입니다. 위의 "멱등성" 항목을 참고하세요.
          schema:
            type: string
            maxLength: 255
          example: 550e8400-e29b-41d4-a716-446655440000
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateDocumentRequest'
            examples:
              notion:
                summary: 노션 페이지로 문서 생성
                value:
                  kind: notion
                  title: 3분기 제품 로드맵
                  description: 이번 분기의 우선순위와 일정
                  notionUrl: https://www.notion.so/myworkspace/Q3-Roadmap-abc123def456
              post:
                summary: 크리에이터 작성 포스트 생성
                value:
                  kind: post
                  title: 온보딩 시간을 절반으로 줄인 방법
                  description: 짧은 사례 연구
                  content: <p>포스트의 전체 HTML 콘텐츠...</p>
                  status: live
              postHtml:
                summary: 검토를 위해 초안으로 만드는 HTML 포스트
                value:
                  kind: post
                  title: 봄 신제품 출시
                  description: 봄 신제품 출시 랜딩 페이지
                  html: >-
                    <style>.page{font-family:system-ui,sans-serif;color:#171B3A;background:#fff;line-height:1.6}.wrap{max-width:720px;margin:0
                    auto;padding:48px
                    20px}.btn{display:inline-block;padding:12px
                    24px;border-radius:999px;background:#3A57F5;color:#fff;text-decoration:none}</style><div
                    class="page"><div class="wrap"><h1>봄 신제품 출시</h1><p>이번 시즌의
                    새로운 소식을 만나 보세요.</p><a class="btn"
                    href="https://example.com/start">시작하기</a></div></div>
                  status: draft
              form:
                summary: 리드 수집 폼 생성
                value:
                  kind: form
                  title: 영업팀과 상담하기
                  description: 팀에 대해 간단히 알려 주세요
                  collectName: true
                  collectCompany: true
                  collectPhone: false
                  leadFormQuestions:
                    - id: a1B2c3D4e5
                      type: short_text
                      label: 어떤 문제를 해결하고 싶으신가요?
                      required: true
                      order: 0
      responses:
        '201':
          description: 문서가 생성되었습니다.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateDocumentResponse'
        '400':
          description: >
            잘못된 요청입니다. `code`는 다음 중 하나입니다: `VALIDATION_ERROR`(본문이 스키마

            검증에 실패 — 어느 필드인지는 `message` 참고, `kind`가 없거나 알 수 없는 값이면

            `kind: must be one of notion, post, form`으로 표시되며, `content`와 `html`을
            모두

            보내거나 둘 다 보내지 않은 `post`도 같은 방식으로 표시됩니다),

            `IDEMPOTENCY_KEY_REQUIRED`, `IDEMPOTENCY_KEY_TOO_LONG`,
            `INVALID_JSON`, `INVALID_INPUT`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: 'API 키가 없거나 형식이 잘못됐거나 폐기되었습니다 (`code: UNAUTHORIZED`).'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: >
            `PAST_DUE_RESTRICTED`.

            `PLAN_REQUIRED`.

            `INSUFFICIENT_SCOPE` — 키에 `documents:write` 범위가 없습니다.

            `DOCUMENT_LIMIT_REACHED` — 계정이 요금제의 *게시된* 문서 한도에 도달했습니다.

            에러에는 `details`가 없습니다. `status: draft`로 만든 `post`나 `form`에는 이 한도가

            적용되지 않습니다 — 초안은 한도에서 제외됩니다.

            `FORM_FILE_QUESTION_DISABLED` — (`kind: form`) `file` 타입 질문이 제출되었지만
            이 계정에서는

            아직 해당 질문 유형을 사용할 수 없습니다.

            `HTML_MODE_DISABLED` — (`kind: post`, `html`) 이 계정에서는 HTML 포스트를 사용할
            수

            없습니다. 재시도할 수 없으며 아무것도 생성되지 않습니다. 대신 `content`로 포스트를

            만들거나, 계정에서 HTML 포스트를 쓸 수 있도록 요청하세요.

            같은 `Idempotency-Key`로 다시 보내면 키가 만료될 때까지(7일) 같은 403이 반환되므로, HTML

            포스트가 활성화된 뒤에는 새 키를 쓰세요.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                documentLimit:
                  value:
                    error:
                      code: DOCUMENT_LIMIT_REACHED
                      message: Document limit reached for your plan
                      retryable: false
                htmlModeDisabled:
                  value:
                    error:
                      code: HTML_MODE_DISABLED
                      message: >-
                        HTML posts are not enabled for this account, so nothing
                        was created. Create the post with `content` (rich text)
                        instead, or ask to have HTML posts enabled
                      retryable: false
        '404':
          description: |
            API는 일부 계정에서만 사용이 가능하며, 요금제와는 별개입니다. 사용할 수
            있어야 하는 계정이라면 DocuRise 담당자에게 문의하세요 (`code: NOT_FOUND`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: |
            `IDEMPOTENCY_KEY_CONFLICT`(재시도 불가 — 같은 키가 이미 다른 요청 본문으로 사용됨,
            새 키를 사용하세요) 또는 `IDEMPOTENCY_KEY_IN_PROGRESS`(재시도 가능 — 이 키로 보낸
            요청이 아직 처리 중, `retryAfterSeconds`가 백오프를 제안합니다).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: >
            `NOTION_FETCH_FAILED` — (`kind: notion`) 노션 페이지를 가져오지 못했습니다.

            `error.details.notionErrorCode`가 구체적인 사유를 알려줍니다(스키마 참고). 재시도 전에

            `retryable`을 확인하세요: 비공개/삭제/거부된 페이지는 재시도해도 가져올 수 없지만, 속도

            제한이나 일시적 장애 코드는 가능합니다.

            `VIDEO_CONTENT_UNSUPPORTED` — (`kind: post`) 본문(`content` 또는
            `html`)에 업로드한 동영상의

            자리표시자(`data-video-id`)가 포함되어 있습니다. 그대로는 재시도할 수 없습니다 — 이를

            제거하거나, 대시보드에서 포스트를 생성/수정하세요. `content`의 YouTube와 Vimeo 임베드

            (`data-video-embed`)는 허용됩니다.

            `EMPTY_BODY` — (`kind: post`, `status: live`) 정제 후 본문(`content` 또는
            `html`)에 실제

            콘텐츠가 남아 있지 않습니다. 그대로는 재시도할 수 없습니다 — 실제 콘텐츠를 추가하거나,

            `status: draft`로 만드세요.

            `UNSUPPORTED_VARIABLE` — (`kind: post`, `status: live`) 본문(`content`
            또는 `html`)에

            게시된 포스트가 사용할 수 없는 이메일 템플릿 변수(예: `{recipient_name}`)가 남아

            있습니다. 그대로는 재시도할 수 없습니다 — 본문에서 제거하세요.

            `STATUS_NOT_SUPPORTED` — `status`가 `inactive`이거나(어떤 종류에서도 생성 시점에는

            지원되지 않습니다. `live`로 만든 뒤 `PATCH`로 `inactive`로 바꾸세요), `kind: notion`에

            `draft`를 보냈습니다(노션 문서에는 초안 상태가 없습니다). 아무것도 생성되지 않습니다.

            `HTML_REJECTED` — (`kind: post`, `html`) 서버가 HTML을 받아들일 수 없었습니다.

            `error.details.reason`이 이유를 알려 줍니다: `too_large`(정제 후 HTML이 포스트 크기

            한도를 넘음), `css_rejected`(`<style>` 블록이 거부됨 — 제거하거나 단순하게 고치고 CSS에

            백슬래시를 쓰지 마세요), `invalid`(서버가 받아들일 수 없는 그 밖의 HTML). 그대로는

            재시도할 수 없으며 아무것도 생성되지 않습니다.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                privatePage:
                  value:
                    error:
                      code: NOTION_FETCH_FAILED
                      message: >-
                        The Notion page could not be fetched — it may be
                        private, deleted, or temporarily unavailable
                      retryable: false
                      details:
                        notionErrorCode: NOTION_ERROR_PRIVATE
                videoUnsupported:
                  value:
                    error:
                      code: VIDEO_CONTENT_UNSUPPORTED
                      message: >-
                        Post content with video embeds is not supported via the
                        API
                      retryable: false
                emptyBody:
                  value:
                    error:
                      code: EMPTY_BODY
                      message: >-
                        The post has no content to publish: add text, an image
                        or a video in the dashboard editor, then try again
                      retryable: false
                unsupportedVariable:
                  value:
                    error:
                      code: UNSUPPORTED_VARIABLE
                      message: >-
                        The post body contains an email variable (for example
                        {recipient_name}) that a published post cannot use:
                        remove it in the dashboard editor, then try again
                      retryable: false
                statusNotSupported:
                  value:
                    error:
                      code: STATUS_NOT_SUPPORTED
                      message: >-
                        Creating a document turned off is not available yet:
                        create it with status live, then PATCH status inactive
                      retryable: false
                htmlRejected:
                  value:
                    error:
                      code: HTML_REJECTED
                      message: >-
                        A <style> block in the HTML was rejected, so nothing was
                        created. Remove or simplify the <style> block (avoid
                        backslashes in CSS) and retry
                      retryable: false
                      details:
                        reason: css_rejected
        '429':
          description: >-
            속도 제한에 걸렸습니다 (`code: RATE_LIMITED`, 항상 재시도 가능): `Retry-After` 헤더와
            `X-RateLimit-*` 헤더를 참고하세요. 또는 동시에 실행 중인 요청이 너무 많습니다 (`code:
            TOO_MANY_CONCURRENT_REQUESTS`, 항상 재시도 가능): `Retry-After`는 `1`이고
            `X-RateLimit-*` 헤더는 없습니다.
          headers:
            Retry-After:
              schema:
                type: integer
            X-RateLimit-Limit:
              schema:
                type: integer
            X-RateLimit-Remaining:
              schema:
                type: integer
            X-RateLimit-Reset:
              schema:
                type: integer
              description: 제한이 초기화되는 유닉스 타임스탬프(초)입니다.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: '`INTERNAL_ERROR`: 예기치 않은 오류. 재시도 가능하며 원인은 반환되지 않습니다.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '503':
          description: |
            서버 측 의존성이 일시적으로 다운되었습니다 — 요청이나 키의 문제가 아닙니다. 항상
            재시도 가능하며, `retryAfterSeconds`가 있으면 그에 따르세요. 다음 중 하나입니다:
            - `RATE_LIMIT_UNAVAILABLE` — 속도 제한기가 일시적으로 다운되었습니다. 호출자가
              제한당한 것이 아닙니다.
            - `SERVICE_UNAVAILABLE` — 서버 측에서 API 키 검증이 일시적으로 불가능합니다. 제시된
              키는 평가되지 않았으며 폐기되거나 무효화된 것이 아닙니다. `retryAfterSeconds`(60)
              이후 동일한 요청을 재시도하세요.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - apiKey: []
components:
  schemas:
    CreateDocumentRequest:
      description: >
        `POST /api/preview/documents`의 본문입니다. `kind`가 세 가지 본문(`notion`, `post`,
        `form`) 중 어느

        것을 쓸지 정합니다. `kind`가 없거나 알 수 없는 값이거나, 다른 종류에 속한 필드를 보내면

        `400 VALIDATION_ERROR`입니다. `post` 본문은 `content`(서식 있는 텍스트 HTML)와
        `html`(HTML 페이지)

        중 정확히 하나만 받습니다. 둘 다 보내거나 둘 다 보내지 않으면

        `400 VALIDATION_ERROR`입니다.
      oneOf:
        - $ref: '#/components/schemas/CreateNotionDocumentRequest'
        - $ref: '#/components/schemas/CreatePostRequest'
        - $ref: '#/components/schemas/CreateFormRequest'
      discriminator:
        propertyName: kind
        mapping:
          notion: '#/components/schemas/CreateNotionDocumentRequest'
          post: '#/components/schemas/CreatePostRequest'
          form: '#/components/schemas/CreateFormRequest'
    CreateDocumentResponse:
      type: object
      required:
        - id
        - url
      properties:
        id:
          type: string
          description: 생성된 문서의 불투명한(opaque) 식별자입니다 — 위의 "식별자" 항목을 참고하세요.
        url:
          type: string
          format: uri
          description: |
            문서의 공개 URL입니다. 즉시 접근 가능합니다. 단, `status: draft`로 만든 `post`나 `form`은
            게시하기 전까지 공개적으로 접근할 수 없습니다.
      example:
        id: a1b2c3d4-e5f6-4789-a012-3456789abcde
        url: https://docurise.io/v/q3-product-roadmap
    ErrorResponse:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
            - retryable
          properties:
            code:
              type: string
              description: 안정적인 기계 판독용 에러 코드입니다 — `message`가 아니라 이 값을 기준으로 분기하세요.
            message:
              type: string
              description: 사람이 읽을 수 있는 상세 설명입니다. 예고 없이 문구가 바뀔 수 있습니다.
            retryable:
              type: boolean
              description: 동일한 요청을 재시도했을 때 성공할 가능성이 있는지 여부입니다.
            retryAfterSeconds:
              type: integer
              description: 구체적인 백오프 힌트가 있는 경우에만 존재합니다.
            details:
              type: object
              description: >
                일부 특정 코드에만 존재합니다:

                - `PROTECTION_RULE_CONFLICT`: `{ rule: string }`, 요청을 거부한 보호
                규칙입니다
                  (`form`, `restricted_access`, `paid_lead_gate`, `free_lead_gate` 또는 `form_no_access_controls`).
                  열려 있는 값입니다. 새 값을 허용하세요.
                - `NEXT_POST_RULE_EXISTS`: `{ ruleId: string }`, 이미 있는 "다음 게시물"
                규칙입니다.

                - `NOTION_FETCH_FAILED`: `{ notionErrorCode: string }`, 다음 중
                하나입니다:
                  `NOTION_ERROR_PRIVATE`, `NOTION_ERROR_NOT_FOUND`,
                  `NOTION_ERROR_NETWORK`, `NOTION_ERROR_RATE_LIMIT`,
                  `NOTION_ERROR_TEMPORARILY_UNAVAILABLE`,
                  `NOTION_ERROR_PAGE_REJECTED`, `NOTION_ERROR_UNKNOWN`.
                - `HTML_REJECTED`: `{ reason: string }`, `too_large`,
                `css_rejected`, `invalid` 중 하나입니다.
                  열려 있는 값입니다. 새 값을 허용하세요.
                - `CONDITION_NOT_SUPPORTED`: `{ field: string, path: string }`,
                받지 않는 조건 타입과 그 조건이
                  요청에서 있는 위치입니다. 그 위치에서 거부된 조건 형태에는 `operator: string`이,
                  `requiredConditions`에서 대신 쓸 수 있는 연산자가 있으면 `invertTo: string`도 더해집니다.
                - `REFERENCE_NOT_FOUND`: `{ path: string, kind: string }`, 알 수
                없는 id가 요청에서 있는 위치와
                  그 id가 가리키는 대상(`document`, `product`, `tier`, `cta`, `folder`, `broadcast`, `step`)입니다.
                  열려 있는 값입니다. 새 값을 허용하세요.
                - `TAG_NOT_FOUND`(그룹 규칙에서): `{ tag: string }`, 존재하지 않는 태그 이름입니다.

                - `GROUP_REFERENCE_NOT_FOUND`: `{ groupIds: string[] }`, 계정에 없는
                그룹 id입니다.

                - `GROUP_LIMIT_REACHED`: `{ limit: integer }`, API로 계정이 만들 수 있는
                그룹의 최대 개수입니다.
          description: >-
            오류 정보입니다. API의 모든 오류 응답에 들어 있으며 `code`, `message`, `retryable`을 담고,
            일부 코드에는 `retryAfterSeconds`와 `details`도 담깁니다.
    CreateNotionDocumentRequest:
      type: object
      title: Notion 페이지
      required:
        - kind
        - title
        - notionUrl
      properties:
        kind:
          type: string
          enum:
            - notion
          description: 문서 종류를 정합니다. 이 본문에서는 항상 `notion`입니다.
        title:
          type: string
          minLength: 1
          maxLength: 200
          description: 문서 제목으로, 공개 페이지에 표시됩니다.
        description:
          type: string
          maxLength: 500
          description: 공개 페이지에 표시되는 선택 사항 부제목입니다.
        notionUrl:
          type: string
          format: uri
          description: |
            웹에 게시(공개 공유)된 노션 페이지의 URL입니다. DocuRise는 이를 인증 없이
            가져옵니다 — 설정할 계정별 노션 연동이 없습니다.
        status:
          type: string
          enum:
            - live
          default: live
          description: >
            `live`(기본값)만 받습니다. 노션 문서에는 초안 상태가 없으므로 `draft`는

            `422 STATUS_NOT_SUPPORTED`이고, 꺼진 상태의 문서를 만드는 것은 아직 지원되지 않으므로

            `inactive`도 `422 STATUS_NOT_SUPPORTED`입니다(문서를 만든 뒤 `PATCH`로
            `status`를

            `inactive`로 바꾸세요).
      additionalProperties: false
    CreatePostRequest:
      type: object
      description: |
        포스트입니다. `content`(서식 있는 텍스트 HTML)와 `html`(HTML 페이지) 중 정확히 하나를
        보내세요. 둘 다 보내거나 둘 다 보내지 않으면 `400 VALIDATION_ERROR`입니다.
      required:
        - kind
        - title
      oneOf:
        - title: '포스트: content (서식 있는 텍스트)'
          required:
            - content
          properties:
            content: {}
        - title: '포스트: html (HTML 페이지)'
          required:
            - html
          properties:
            html: {}
      properties:
        kind:
          type: string
          enum:
            - post
          description: 문서 종류를 정합니다. 이 본문에서는 항상 `post`입니다.
        title:
          type: string
          minLength: 1
          maxLength: 200
          description: 문서 제목으로, 공개 페이지에 표시됩니다.
        description:
          type: string
          maxLength: 500
          description: 공개 페이지에 표시되는 선택 사항 부제목입니다.
        content:
          type: string
          maxLength: 200000
          description: |
            포스트의 HTML 콘텐츠입니다. 뷰어가 렌더링할 때 쓰는 것과 동일한 허용 목록으로 저장
            시점에 서버 측에서 정제(sanitize)됩니다. YouTube와 Vimeo의 `data-video-embed`
            자리표시자는 유지되고(작업 설명의 `post` 항목 참고), 업로드한 동영상의 자리표시자
            (`data-video-id`)는 거부됩니다 — `422 VIDEO_CONTENT_UNSUPPORTED`를 참고하세요.
            `status: live`인 경우, 정제 후
            콘텐츠가 비어 있으면 `422 EMPTY_BODY`, 이메일 템플릿 변수(예: `{recipient_name}`)가
            남아 있으면 `422 UNSUPPORTED_VARIABLE`입니다 — `status: draft`는 둘 다 적용되지
            않습니다. `content`와 `html` 중 하나만 보내세요.
        html:
          type: string
          maxLength: 200000
          description: |
            포스트의 HTML 페이지로, 자체 `<style>` 블록을 포함한 하나의 HTML 조각이며 최대 200,000자입니다.
            `html`과 `content` 중 하나만 보내세요(200,000자를 넘거나 NUL 문자가 있으면
            `400 VALIDATION_ERROR`). 저장하거나 표시하기 전에 서버에서 정제됩니다:
            스크립트, 이벤트 핸들러, 폼, 입력란, 버튼, 선택 상자, iframe은 제거되고, `<style>` 블록과
            인라인 `style` 속성은 선언 필터를 거친 뒤 유지되며, 상대 이미지 경로는 제거됩니다(이미지는
            절대 `https://` URL 또는 `data:image`여야 합니다). 응답에는 무엇이 제거되었는지 나타나지
            않습니다. 계정에서 HTML 포스트를 사용할 수 있어야 하며(`403 HTML_MODE_DISABLED`), 서버가
            HTML을 `422 HTML_REJECTED`로 거부할 수 있습니다. `status: live`이면 사람의 검토 없이 즉시
            게시됩니다. 사람이 먼저 검토하게 하려면 `status: draft`로 만든 뒤 대시보드에서
            게시하세요. 생성 작업의 "HTML 포스트" 항목을 참고하세요.
        status:
          type: string
          enum:
            - live
            - draft
          default: live
          description: |
            `live`(기본값)는 문서를 게시합니다. `draft`는 저장 전용으로 만들며, 공개적으로 접근할 수
            없고 요금제의 게시된 문서 한도에서 제외됩니다. `PATCH /api/preview/documents/{id}`에
            `status: live`를 보내거나 대시보드에서 게시하세요. `inactive`는
            `422 STATUS_NOT_SUPPORTED`입니다(꺼진 상태의 문서를 만드는 것은 아직 지원되지 않습니다.
            `live`로 만든 뒤 `PATCH`로 `status`를 `inactive`로 바꾸세요).
      additionalProperties: false
    CreateFormRequest:
      type: object
      title: 리드 수집 폼
      required:
        - kind
        - title
      properties:
        kind:
          type: string
          enum:
            - form
          description: 문서 종류를 정합니다. 이 본문에서는 항상 `form`입니다.
        title:
          type: string
          minLength: 1
          maxLength: 200
          description: 문서 제목으로, 공개 페이지에 표시됩니다.
        description:
          type: string
          maxLength: 500
          description: 공개 페이지에 표시되는 선택 사항 부제목입니다.
        status:
          type: string
          enum:
            - live
            - draft
          default: live
          description: |
            `live`(기본값)는 문서를 게시합니다. `draft`는 저장 전용으로 만들며, 공개적으로 접근할 수
            없고 요금제의 게시된 문서 한도에서 제외됩니다. `PATCH /api/preview/documents/{id}`에
            `status: live`를 보내거나 대시보드에서 게시하세요. `inactive`는
            `422 STATUS_NOT_SUPPORTED`입니다(꺼진 상태의 문서를 만드는 것은 아직 지원되지 않습니다.
            `live`로 만든 뒤 `PATCH`로 `status`를 `inactive`로 바꾸세요).
        collectName:
          type: boolean
          default: false
          description: 제출 시 방문자의 이름을 수집합니다.
        collectCompany:
          type: boolean
          default: false
          description: 제출 시 방문자의 회사명을 수집합니다.
        collectPhone:
          type: boolean
          default: false
          description: 제출 시 방문자의 전화번호를 수집합니다.
        leadFormQuestions:
          type: array
          default: []
          maxItems: 10
          items:
            $ref: '#/components/schemas/LeadFormQuestion'
          description: 커스텀 질문입니다. 항목에 정의되지 않은 키가 있으면 `400 VALIDATION_ERROR`로 거부됩니다.
      additionalProperties: false
    LeadFormQuestion:
      type: object
      required:
        - id
        - type
        - label
        - order
      properties:
        id:
          type: string
          description: 클라이언트에서 생성한 불투명한 질문 ID입니다(예를 들어 10자리 nanoid). 배열 내에서 고유해야 합니다.
        type:
          type: string
          enum:
            - short_text
            - long_text
            - single_choice
            - multi_choice
            - date
            - file
          description: |
            `file` 질문은 내 계정에서 사용할 수 없을 수 있습니다 — `403
            FORM_FILE_QUESTION_DISABLED`를 참고하세요.
        label:
          type: string
          minLength: 1
          maxLength: 200
          description: 방문자에게 보이는 질문 문구이며 1자 이상 200자 이하입니다.
        required:
          type: boolean
          default: false
          description: 제출하려면 방문자가 반드시 답해야 하는지 여부입니다. 기본값은 `false`입니다.
        useForSegmentation:
          type: boolean
          default: false
          description: >-
            선택형(`single_choice`/`multi_choice`)에만 해당합니다. 응답을 그룹 필터링이 가능한 커스텀 필드에
            저장합니다.
        allowOther:
          type: boolean
          default: false
          description: 선택형에만 해당합니다. 자유 텍스트 "기타" 직접 입력 옵션을 추가합니다.
        description:
          type: string
          maxLength: 500
          description: 질문 라벨 아래에 표시되는 선택 사항 도움말 텍스트입니다.
        choices:
          type: array
          maxItems: 10
          items:
            type: string
            minLength: 1
            maxLength: 200
          description: '`single_choice`/`multi_choice`에는 필수(최소 1개) — 다른 타입에서는 무시됩니다.'
        order:
          type: integer
          minimum: 0
          description: 문서 내 질문들 사이에서의 표시 순서입니다.
      additionalProperties: false
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      bearerFormat: dr_live_<keyId>_<secret>
      description: |
        Settings → Developers에서 발급한 키를 `Authorization: Bearer
        dr_live_<12자리 16진수>_<64자리 16진수>` 형태로 전송합니다. 키는 명시적인 범위를
        가지며 — 각 엔드포인트에 필요한 범위가 해당 엔드포인트에 명시되어 있습니다. 전체 키는 발급 시점에 딱 한
        번만 표시되며 다시 조회할 수 없습니다 — 이후에는 `keyId` 접두어만 표시됩니다. 폐기된
        키는 바로 다음 요청부터 인증에 실패합니다.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.