> ## 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.

# Create a document

> Create a DocuRise document: a Notion page, a post (rich text or an HTML page) or a lead-capture form



## OpenAPI

````yaml api-reference/openapi.yaml POST /api/preview/documents
openapi: 3.1.0
info:
  title: DocuRise Public API
  version: preview
  description: >
    Programmatic access to DocuRise.


    **This is a preview API.** Every path is versioned under `/api/preview/`

    and carries no compatibility guarantee while it is in preview: fields,

    values and error codes may be added, changed or removed. Once the

    contract stabilizes it will be promoted to `/api/v1/`. See the

    [changelog](/changelog) for what changed and when.


    ### Access


    Every route requires:

    1. A valid API key (see Authentication below).

    2. The calling account's plan must be **Iconic or above**.

    3. During the preview the API is available to some accounts only, whatever
       the plan. Accounts without access get a `404` from every route below; if
       you expect access, check with your DocuRise contact.

    Generate and manage keys from **Settings → Developers** in the dashboard.


    ### Idempotency


    `POST` endpoints that create a resource require an `Idempotency-Key`

    header. Reusing the same key with the same request body replays the

    original response (same status code, same body) instead of creating a

    second resource — safe to retry on timeout or connection loss. A key is

    remembered for 7 days; after that the same key is treated as a new

    request. Reusing the same key with a **different** body is rejected with

    `IDEMPOTENCY_KEY_CONFLICT`. Generate a fresh key (a UUID or ULID is fine)

    per logical operation, not per HTTP attempt.


    One exception: `POST /api/preview/tags` takes no `Idempotency-Key`. Creating
    the same

    tag name twice changes nothing (the second call returns the existing tag
    with

    `created: false`), so a retry is always safe.


    Another exception: `POST /api/preview/segments/preview` takes no
    `Idempotency-Key`.

    It saves nothing, so repeating it is always safe.


    ### Response compatibility


    Responses may gain additional fields over time, and `type`/`code`-style

    enum values (for example a future `LeadFormQuestion.type` member, or a

    new error `code`) may gain new members. Always ignore fields and enum

    values you don't recognize rather than treating them as errors — this is

    what makes every additive change under `preview` non-breaking.


    A field absent from a response is not always the same as a field present

    with value `null`. A field whose type never includes `null` is omitted

    when it doesn't apply — it is never sent as `null` (for example,

    `retryAfterSeconds` is left out entirely when there's no backoff hint, not

    set to `null`). A field whose type explicitly allows `null` uses it to

    mean something real and distinct from "not present" (for example,

    `expiresAt: null` on a document means it has no fixed expiry date, not

    "unknown" or "not applicable").


    All field names use `camelCase` — `notionUrl`, `leadFormQuestions`,

    `collectName`, and so on — consistently across every request and

    response body.


    ### Identifiers


    `id` values are opaque strings. Store and compare them as unstructured

    tokens — don't parse them, validate their format, or assume anything

    about their length or character set.


    ### Errors


    Every error response has the same shape:


    ```json

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

    ```


    Switch on `code`, not `message` — `message` is free text for logs/debugging

    and may be reworded without notice; `code` is the stable contract.
    `retryable`

    tells you whether the same request is safe to retry as-is; when present,

    `retryAfterSeconds` is a concrete backoff hint.


    A request body that contains a NUL character (`\u0000`), in a string value

    or in a property name, is refused with `400 VALIDATION_ERROR` before
    anything

    is saved.


    ### Rate limiting


    60 requests/minute per account across all `/api/preview/*` routes,

    regardless of how many keys that account has. Exceeding it returns `429`

    with a `Retry-After` header.


    Separately, an account can have at most 10 requests running at the same

    moment across these routes, however many keys it has. A request that would

    be the eleventh returns `429` with `code: TOO_MANY_CONCURRENT_REQUESTS` and

    `Retry-After: 1`; wait for one of your requests to finish and retry. This

    limit counts requests that have not finished, so it can apply even while

    you are under 60 requests per minute.
  contact:
    name: DocuRise
    url: https://docurise.io
servers:
  - url: https://docurise.io
    description: Production
security:
  - apiKey: []
tags:
  - name: Documents
    description: >-
      Create documents (a Notion page, a post or a lead-capture form), list or
      read the documents you own, publish drafts, and change their status,
      expiry and email protection.
  - name: Analytics
    description: >-
      Read aggregate view, visitor, lead and conversion numbers for one of your
      documents, split by traffic channel, compared with the previous period, or
      as a form funnel, and rank all of your documents by views, visitors or
      leads.
  - name: Folders
    description: Read your folders and courses, with item counts.
  - name: Products
    description: Read your product catalog (read-only).
  - name: Audiences
    description: >-
      List your tags and groups with contact counts, create a group from a rule,
      count who a rule would match, and read one group with its rule. Counts
      only; no member data is ever returned.
  - name: Contacts
    description: >-
      Add or remove a tag on one of your contacts. Contact details are never
      returned.
  - name: Email
    description: >-
      Read email broadcasts, their aggregate delivery numbers, and automations.
      Email only; Kakao is not included.
  - name: Instagram
    description: >-
      Create, read, edit, pause and resume Instagram comment-to-DM automation
      rules.
  - name: Account
    description: Inspect the API key you are calling with.
paths:
  /api/preview/documents:
    post:
      tags:
        - Documents
      summary: Create a document
      description: >
        Creates a new DocuRise document of one of three kinds and returns its
        share

        URL. Requires the `documents:write` scope on the presented API key.


        The required `kind` field selects the kind, and with it the other fields

        that apply. A field that belongs to another kind is a `400
        VALIDATION_ERROR`,

        and so is a missing or unknown `kind` (its message names `kind`).


        | `kind` | Creates | Fields |

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

        | `notion` | A document backed by a public Notion page | `title`,
        `description`, `notionUrl`, `status` |

        | `post` | A creator-authored post | `title`, `description`, exactly one
        of `content` or `html`, `status` |

        | `form` | A standalone lead-capture form | `title`, `description`,
        `status`, `collectName`, `collectCompany`, `collectPhone`,
        `leadFormQuestions` |


        A text field (`title`, `description`, a post's `content` or `html`, a
        lead-form question's `label`,

        `description` or `choices`) must not contain a NUL (U+0000) character:
        it is a `400 VALIDATION_ERROR`

        naming the field, and nothing is created. Every other character is
        accepted.


        **`notion`.** The Notion page must already be published to web (shared

        publicly); DocuRise does not use per-account Notion OAuth and cannot
        read a

        private page. The page is fetched and validated before the response is

        returned, so a `201` means the document is live at `url` immediately.


        **`post`.** A creator-authored post. Send the body in

        exactly one of two fields: `content` (rich-text HTML) or `html` (an HTML
        page, see

        "HTML posts" below). Sending both, or neither, is a `400
        VALIDATION_ERROR` and nothing

        is created.


        A YouTube or Vimeo video is embedded in `content` with a placeholder:

        `<div data-video-embed="youtube:VIDEO_ID"></div>` (the 11-character
        video id),

        `<div data-video-embed="vimeo:VIDEO_ID"></div>` (digits) or, for an
        unlisted Vimeo

        video, `<div data-video-embed="vimeo:VIDEO_ID:HASH"></div>` (the hash is
        6 to 20

        lowercase hex characters). Any other value is dropped when the content
        is

        sanitized, and so is an `<iframe>`, so pasted embed code or a bare URL
        does not

        create an embed. A post whose only content is an embed counts as having
        content.


        The body is checked after sanitizing, whichever field carried it. A body
        containing a

        placeholder for an **uploaded** video (`data-video-id`) is rejected with

        `422 VIDEO_CONTENT_UNSUPPORTED`, because a post created through the API
        has no draft to

        attach an uploaded video to. A `post` created with

        the default `status: live` must have real content — an empty or
        sanitizer-stripped body

        is `422 EMPTY_BODY`, and one containing an email-template variable like

        `{recipient_name}` is `422 UNSUPPORTED_VARIABLE` — the same gate

        `PATCH /api/preview/documents/{id}` enforces when publishing a draft; a

        `status: draft` post is exempt from both, same as that publish gate.


        **HTML posts (`html`).** `html` carries a whole page as one HTML
        fragment, with its own

        `<style>` blocks, layout and colors, up to 200,000 characters (more is a

        `400 VALIDATION_ERROR` naming `html`). Use it for pages that

        rich text cannot express. It is available to accounts that have HTML
        posts enabled; for any

        other account the request is `403 HTML_MODE_DISABLED` and nothing is
        created (create the post

        with `content` instead). The `status` rules above apply unchanged, and
        the response is the

        same `{ id, url }`.


        **With `status: live` an HTML post is published immediately, with no
        human review.** To have

        a person look at the page first, create it with `status: draft` and
        publish it from the

        dashboard (or with `PATCH /api/preview/documents/{id}` and `status:
        live`).


        The server sanitizes the HTML before it is stored or shown, and the
        response does not list

        what was removed. Write to what is kept:


        - **Tags.** The tags of rich-text `content`, plus `header`, `nav`,
        `main`, `section`,
          `article`, `aside`, `footer`, `figure`, `figcaption`, `address`, `small` and `style`.
          Anything else is removed, in particular `script`, `iframe`, `object`, `embed`, `link`,
          `meta`, `base`, `template`, `svg`, `canvas`, `video`, `audio`, `picture`, and every
          form control (`form`, `input`, `button`, `select`, `textarea`). Scripts and event
          handlers never run; use CSS for interactivity (`:hover`, `:target`, animations), and a
          link styled as a button instead of `<button>`.
        - **Attributes.** A fixed allow-list (including `class`, `id`, `style`,
        `href`, `src`,
          `alt`, `width`, `height` and `aria-*`). Everything else is removed, including every `on*`
          handler and all `data-*` attributes except the editor's own markers (`data-video-id`,
          `data-video-embed`, `data-subscribe-button`), which survive only with a valid value.
          A `data-video-id` marker is refused (`VIDEO_CONTENT_UNSUPPORTED`), and a
          `data-subscribe-button` is kept only for a subscribe form you own, for at most 20
          distinct forms per post. A link's `href` must be `https://`, `http://`, `mailto:`,
          `tel:`, `#anchor` or `/path`.
        - **Images.** `src` must be an absolute `https://` URL or a `data:image`
        PNG, JPEG, GIF or
          WebP. A relative path such as `images/a.png` is removed and the image shows nothing.
          Remote images are kept as written and are not copied to DocuRise, so use a URL that
          stays public (not an expiring link). A `data:` image counts toward the 200,000-character
          limit.
        - **CSS.** `<style>` blocks and inline `style` attributes are both kept,
        after a filter:
          `url()` accepts only an absolute `https://` URL or a `data:image` PNG, JPEG, GIF or WebP;
          `@import`, `@font-face`, `@container`, `@page`, CSS nesting and `attr()` are dropped.
          Custom properties (`--name`) are kept and renamed so they cannot collide with
          DocuRise's own. On the page's root element, `position`, `top`/`right`/`bottom`/`left`/`inset`,
          `z-index`, `transform`, `filter`, `overflow`, `contain`, `clip-path`, `mask` and `float`
          are ignored. A stylesheet with a backslash anywhere, a syntax error, or more than
          8 levels of nesting is refused: see `422 HTML_REJECTED`.
        - **Fonts.** Name a font in `font-family` (or the `font` shorthand)
        exactly as listed:
          `"Pretendard"`, `"Paperlogy"` or `"Noto Serif KR"`, in regular (400) and bold (700) only.
          DocuRise serves these itself. Always follow the name with a fallback stack, for example
          `"Pretendard","Apple SD Gothic Neo","Noto Sans KR",system-ui,sans-serif`. Any other font
          name shows in the reader's own font, and font files, `@font-face` and Google Fonts links
          do nothing.
        - **Display.** The page is shown inside the post body, not as a
        standalone document, so
          send a fragment: no `<!DOCTYPE>`, `<html>`, `<head>` or `<body>`, one root element, and
          your own background and font on it (`body`, `html` and `:root` selectors style that
          root). It always sits on a light background, even in dark mode. Width-based media
          queries measure the space the page is shown in, not the screen.

        **`form`.** A standalone lead-capture page with no Notion, PDF or post

        content. A form always asks for the visitor's email and has no

        password, allow list or popup call-to-action. Each `leadFormQuestions`
        item takes

        only the keys of the `LeadFormQuestion` schema; an unknown key is a

        `400 VALIDATION_ERROR`, never silently dropped.


        **Status.** The optional `status` field uses the same vocabulary as

        `PATCH /api/preview/documents/{id}` and defaults to `live`, so omitting
        it

        creates a published document. What each kind may be created with:


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

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

        | `notion` | creates (default) | `422 STATUS_NOT_SUPPORTED` | `422
        STATUS_NOT_SUPPORTED` |

        | `post`, `form` | creates (default) | creates a draft | `422
        STATUS_NOT_SUPPORTED` |


        A `post` or `form` created with `status: draft` is not published and
        does not count toward the published-document limit. Publish a draft with
        `PATCH /api/preview/documents/{id}` and `status: live`, or from the
        dashboard. Creating a document already turned off

        (`status: inactive`) is not available yet for any kind: create it with

        `status: live`, then `PATCH` it to `inactive`. A value outside

        `live | draft | inactive` is a `400 VALIDATION_ERROR` naming `status`.


        **Idempotency.** All three kinds use one `Idempotency-Key` flow. A key

        replays only the same request: reusing it with a different `kind` (or
        any

        other different body) is `409 IDEMPOTENCY_KEY_CONFLICT`.


        The document is created synchronously, so a `201` means it exists

        immediately.
      operationId: createDocument
      parameters:
        - name: Idempotency-Key
          in: header
          required: true
          description: >-
            A unique client-generated string (≤ 255 characters) identifying this
            create attempt. See "Idempotency" above.
          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: A document from a Notion page
                value:
                  kind: notion
                  title: Q3 Product Roadmap
                  description: Priorities and timeline for the quarter
                  notionUrl: https://www.notion.so/myworkspace/Q3-Roadmap-abc123def456
              post:
                summary: A creator-authored post
                value:
                  kind: post
                  title: How we cut onboarding time in half
                  description: A short case study
                  content: <p>Full HTML content of the post...</p>
                  status: live
              postHtml:
                summary: An HTML post, created as a draft for review
                value:
                  kind: post
                  title: Spring launch
                  description: Landing page for the spring launch
                  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>Spring
                    launch</h1><p>Everything new this season.</p><a class="btn"
                    href="https://example.com/start">Get started</a></div></div>
                  status: draft
              form:
                summary: A lead-capture form
                value:
                  kind: form
                  title: Talk to sales
                  description: Tell us a bit about your team
                  collectName: true
                  collectCompany: true
                  collectPhone: false
                  leadFormQuestions:
                    - id: a1B2c3D4e5
                      type: short_text
                      label: What are you hoping to solve?
                      required: true
                      order: 0
      responses:
        '201':
          description: Document created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateDocumentResponse'
        '400':
          description: >
            Malformed request. `code` is one of `VALIDATION_ERROR` (body failed

            schema validation — see `message` for which field; a missing or
            unknown

            `kind` is reported as `kind: must be one of notion, post, form`; a
            `post` that sends

            both `content` and `html`, or neither, is reported the same way),

            `IDEMPOTENCY_KEY_REQUIRED`, `IDEMPOTENCY_KEY_TOO_LONG`,
            `INVALID_JSON`,

            or `INVALID_INPUT`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: 'Missing, malformed, or revoked API key (`code: UNAUTHORIZED`).'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: >
            `PAST_DUE_RESTRICTED`.

            `PLAN_REQUIRED`.

            `INSUFFICIENT_SCOPE` — the key lacks `documents:write`.

            `DOCUMENT_LIMIT_REACHED` — the account has hit its plan's

            *published*-document limit. The error carries no `details`.

            Does not apply to a `post` or `form` created with `status: draft`:

            drafts are exempt from this limit.

            `FORM_FILE_QUESTION_DISABLED` — (`kind: form`) a `file`-type
            question

            was submitted but that question type isn't enabled for this account
            yet.

            `HTML_MODE_DISABLED` — (`kind: post`, `html`) HTML posts are not
            enabled for

            this account. Not retryable; nothing is created. Create the post
            with `content`

            instead, or ask for HTML posts to be enabled for the account. A
            replay with the

            same `Idempotency-Key` returns the same 403 until the key expires (7
            days), so use a

            new key once HTML posts are enabled.
          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: >
            The API is available to some accounts only, whatever the plan. If
            you

            expect access, check with your DocuRise contact (`code: NOT_FOUND`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: |
            `IDEMPOTENCY_KEY_CONFLICT` (not retryable — the same key was
            already used with a different request body; use a new key) or
            `IDEMPOTENCY_KEY_IN_PROGRESS` (retryable — a request with this
            key is still being processed; `retryAfterSeconds` suggests a
            backoff).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: >
            `NOTION_FETCH_FAILED` — (`kind: notion`) the Notion page could not
            be

            fetched. `error.details.notionErrorCode` narrows the reason further
            (see

            schema). Check `retryable` before retrying: a
            private/deleted/rejected

            page will not become fetchable on retry, but a rate-limit or
            transient-outage

            code will.

            `VIDEO_CONTENT_UNSUPPORTED` — (`kind: post`) the body (`content` or
            `html`) contains a

            placeholder for an uploaded video (`data-video-id`). Not retryable
            as-is; remove it, or

            create/edit the post from the dashboard instead. YouTube and Vimeo
            embeds

            (`data-video-embed`) are accepted in `content`.

            `EMPTY_BODY` — (`kind: post`, `status: live`) the body (`content` or
            `html`) has no

            real content after sanitization. Not retryable as-is; add real
            content, or create it with

            `status: draft` instead.

            `UNSUPPORTED_VARIABLE` — (`kind: post`, `status: live`) the body
            (`content` or

            `html`) contains an email-template variable (e.g.
            `{recipient_name}`) a published

            post can't use. Not retryable as-is; remove it from the body.

            `STATUS_NOT_SUPPORTED` — `status` is `inactive` (not available at
            create time for

            any kind: create the document `live`, then `PATCH` it to
            `inactive`), or `draft`

            on `kind: notion` (a Notion document has no draft state). Nothing is
            created.

            `HTML_REJECTED` — (`kind: post`, `html`) the server could not accept
            the HTML.

            `error.details.reason` says why: `too_large` (the HTML is over the
            post size limit

            once sanitized), `css_rejected` (a `<style>` block was refused:
            remove or simplify

            it, and avoid backslashes in CSS) or `invalid` (any other HTML the
            server could not

            accept). Not retryable as-is; nothing is created.
          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: >-
            Rate limited (`code: RATE_LIMITED`, always retryable): see the
            `Retry-After` header and `X-RateLimit-*` headers. Or too many
            requests running at once (`code: TOO_MANY_CONCURRENT_REQUESTS`,
            always retryable): `Retry-After` is `1` and there are no
            `X-RateLimit-*` headers.
          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: Unix timestamp (seconds) when the limit resets.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: >-
            `INTERNAL_ERROR`: unexpected failure. Retryable; the cause is not
            returned.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '503':
          description: |
            A server-side dependency is temporarily down; this is not a
            problem with the caller's request or key. Always retryable —
            honor `retryAfterSeconds` when present. One of:
            - `RATE_LIMIT_UNAVAILABLE` — the rate limiter is temporarily
              down; this is not the caller being throttled.
            - `SERVICE_UNAVAILABLE` — API key verification is temporarily
              unavailable on the server side. The presented key was not
              evaluated and is not revoked or invalid; retry the same
              request after `retryAfterSeconds` (60).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - apiKey: []
components:
  schemas:
    CreateDocumentRequest:
      description: >
        The body of `POST /api/preview/documents`. `kind` selects which of the
        three bodies applies:

        `notion`, `post` or `form`. A missing or unknown `kind`, or a field that
        belongs to another kind, is

        a `400 VALIDATION_ERROR`. A `post` body takes exactly one of `content`
        (rich-text HTML) or `html`

        (an HTML page): sending both or neither is a `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 identifier for the created document — see "Identifiers"
            above.
        url:
          type: string
          format: uri
          description: >
            The document's public share URL. Live immediately, except for a
            `post` or `form` created

            with `status: draft`, which is not publicly reachable until
            published.
      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: >-
                Stable machine-readable error code — switch on this, not
                `message`.
            message:
              type: string
              description: Human-readable detail. May change wording without notice.
            retryable:
              type: boolean
              description: Whether retrying the identical request may succeed.
            retryAfterSeconds:
              type: integer
              description: Present only when a concrete backoff hint applies.
            details:
              type: object
              description: >
                Present only for a few specific codes:

                - `PROTECTION_RULE_CONFLICT`: `{ rule: string }`, the protection
                rule that refused the request
                  (`form`, `restricted_access`, `paid_lead_gate`, `free_lead_gate` or `form_no_access_controls`).
                  Open-ended: tolerate new values.
                - `NEXT_POST_RULE_EXISTS`: `{ ruleId: string }`, the existing
                next-post rule.

                - `NOTION_FETCH_FAILED`: `{ notionErrorCode: string }`, one of
                  `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 }`, one of `too_large`,
                `css_rejected` or `invalid`.
                  Open-ended: tolerate new values.
                - `CONDITION_NOT_SUPPORTED`: `{ field: string, path: string }`,
                the condition type that is not
                  accepted and where it sits in the request. For a condition shape refused where it sits
                  also `operator: string` and, when there is an equivalent operator for `requiredConditions`,
                  `invertTo: string`.
                - `REFERENCE_NOT_FOUND`: `{ path: string, kind: string }`, where
                the unknown id sits in the
                  request and what it refers to: `document`, `product`, `tier`, `cta`, `folder`, `broadcast` or
                  `step`. Open-ended: tolerate new values.
                - `TAG_NOT_FOUND` (from a group rule): `{ tag: string }`, the
                tag name that does not exist.

                - `GROUP_REFERENCE_NOT_FOUND`: `{ groupIds: string[] }`, the
                group ids that are not in the account.

                - `GROUP_LIMIT_REACHED`: `{ limit: integer }`, the most groups
                an account can create through the API.
          description: >-
            The error. Every error response from the API has it: `code`,
            `message`, `retryable` and, for some codes, `retryAfterSeconds` and
            `details`.
    CreateNotionDocumentRequest:
      type: object
      title: Notion page
      required:
        - kind
        - title
        - notionUrl
      properties:
        kind:
          type: string
          enum:
            - notion
          description: Selects the document kind. Always `notion` for this body.
        title:
          type: string
          minLength: 1
          maxLength: 200
          description: Document title, shown on the share page.
        description:
          type: string
          maxLength: 500
          description: Optional subtitle shown on the share page.
        notionUrl:
          type: string
          format: uri
          description: |
            A URL to a Notion page that has been published to web (shared
            publicly). DocuRise fetches it unauthenticated — there is no
            per-account Notion connection to configure.
        status:
          type: string
          enum:
            - live
          default: live
          description: >
            Only `live` (the default) is accepted. `draft` is

            `422 STATUS_NOT_SUPPORTED` because a Notion document has no draft
            state, and

            `inactive` is `422 STATUS_NOT_SUPPORTED` because creating a document
            turned off

            is not available yet (create it, then `PATCH` its `status` to
            `inactive`).
      additionalProperties: false
    CreatePostRequest:
      type: object
      description: >
        A post. Send exactly one of `content` (rich-text HTML) or `html` (an
        HTML page): both or

        neither is a `400 VALIDATION_ERROR`.
      required:
        - kind
        - title
      oneOf:
        - title: 'Post: content (rich text)'
          required:
            - content
          properties:
            content: {}
        - title: 'Post: html (HTML page)'
          required:
            - html
          properties:
            html: {}
      properties:
        kind:
          type: string
          enum:
            - post
          description: Selects the document kind. Always `post` for this body.
        title:
          type: string
          minLength: 1
          maxLength: 200
          description: Document title, shown on the share page.
        description:
          type: string
          maxLength: 500
          description: Optional subtitle shown on the share page.
        content:
          type: string
          maxLength: 200000
          description: >
            HTML content of the post. Sanitized server-side with the same

            allow-list the viewer renders before persisting. A YouTube or Vimeo

            `data-video-embed` placeholder is kept (see `post` under `kind` in
            the

            operation description); a placeholder for an uploaded video

            (`data-video-id`) is rejected — see `422 VIDEO_CONTENT_UNSUPPORTED`.
            For

            `status: live`, empty/sanitizer-stripped content is

            `422 EMPTY_BODY` and an email-template variable (e.g.

            `{recipient_name}`) is `422 UNSUPPORTED_VARIABLE` — `status: draft`

            is exempt from both. Send either `content` or `html`, not both.
        html:
          type: string
          maxLength: 200000
          description: >
            An HTML page for the post, as one fragment with its own `<style>`
            blocks, up to 200,000

            characters (more is `400 VALIDATION_ERROR`; so is a NUL character).
            Send either `html`

            or `content`, not both. Sanitized on the server before it

            is stored or shown: scripts, event handlers, forms, inputs, buttons,
            selects and iframes

            are removed, `<style>` blocks and inline `style` attributes are kept
            after a declaration

            filter, and relative image paths are removed (images must be
            absolute `https://` URLs

            or `data:image`). The response does not list what was removed. Needs
            HTML posts to be

            enabled for the account (`403 HTML_MODE_DISABLED`); the server can
            refuse the HTML with

            `422 HTML_REJECTED`. With `status: live` it is published immediately
            with no human

            review; create it with `status: draft` and publish from the
            dashboard to have a person

            review it first. See "HTML posts" in the create operation.
        status:
          type: string
          enum:
            - live
            - draft
          default: live
          description: >
            `live` (the default) publishes the document. `draft` makes it
            storage-only: not

            publicly reachable and exempt from the plan's published-document
            limit; publish it

            with `PATCH /api/preview/documents/{id}` and `status: live`, or from
            the dashboard.

            `inactive` is `422 STATUS_NOT_SUPPORTED` (creating a document turned
            off is not

            available yet: create it `live`, then `PATCH` its `status` to
            `inactive`).
      additionalProperties: false
    CreateFormRequest:
      type: object
      title: Lead-capture form
      required:
        - kind
        - title
      properties:
        kind:
          type: string
          enum:
            - form
          description: Selects the document kind. Always `form` for this body.
        title:
          type: string
          minLength: 1
          maxLength: 200
          description: Document title, shown on the share page.
        description:
          type: string
          maxLength: 500
          description: Optional subtitle shown on the share page.
        status:
          type: string
          enum:
            - live
            - draft
          default: live
          description: >
            `live` (the default) publishes the document. `draft` makes it
            storage-only: not

            publicly reachable and exempt from the plan's published-document
            limit; publish it

            with `PATCH /api/preview/documents/{id}` and `status: live`, or from
            the dashboard.

            `inactive` is `422 STATUS_NOT_SUPPORTED` (creating a document turned
            off is not

            available yet: create it `live`, then `PATCH` its `status` to
            `inactive`).
        collectName:
          type: boolean
          default: false
          description: Collect the viewer's name at submission.
        collectCompany:
          type: boolean
          default: false
          description: Collect the viewer's company at submission.
        collectPhone:
          type: boolean
          default: false
          description: Collect the viewer's phone number at submission.
        leadFormQuestions:
          type: array
          default: []
          maxItems: 10
          items:
            $ref: '#/components/schemas/LeadFormQuestion'
          description: >-
            Custom questions. A key an item does not define is rejected with
            `400 VALIDATION_ERROR`.
      additionalProperties: false
    LeadFormQuestion:
      type: object
      required:
        - id
        - type
        - label
        - order
      properties:
        id:
          type: string
          description: >-
            Client-generated opaque question ID (e.g. a 10-character nanoid).
            Must be unique within the array.
        type:
          type: string
          enum:
            - short_text
            - long_text
            - single_choice
            - multi_choice
            - date
            - file
          description: |
            `file` questions may not be enabled for your account — see
            `403 FORM_FILE_QUESTION_DISABLED`.
        label:
          type: string
          minLength: 1
          maxLength: 200
          description: The question text shown to the visitor, 1 to 200 characters.
        required:
          type: boolean
          default: false
          description: >-
            Whether the visitor must answer before submitting. Defaults to
            `false`.
        useForSegmentation:
          type: boolean
          default: false
          description: >-
            Choice-types only (`single_choice`/`multi_choice`). Persists answers
            to group-filterable custom fields.
        allowOther:
          type: boolean
          default: false
          description: Choice-types only. Adds a free-text "Other" write-in option.
        description:
          type: string
          maxLength: 500
          description: Optional helper text shown below the question label.
        choices:
          type: array
          maxItems: 10
          items:
            type: string
            minLength: 1
            maxLength: 200
          description: >-
            Required (at least one) for `single_choice`/`multi_choice`; ignored
            for other types.
        order:
          type: integer
          minimum: 0
          description: Display order among the document's questions.
      additionalProperties: false
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      bearerFormat: dr_live_<keyId>_<secret>
      description: |
        A key generated from Settings → Developers, sent as
        `Authorization: Bearer dr_live_<12 hex chars>_<64 hex chars>`. Keys
        carry explicit scopes — each endpoint states the one it requires.
        The full key is shown exactly once at generation time and cannot be
        retrieved again; only the `keyId` prefix is shown afterward. A
        revoked key fails auth on its very next request.

````

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