> ## 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 an Instagram rule

> Create the next-post DM automation rule, armed or paused



## OpenAPI

````yaml api-reference/openapi.yaml POST /api/preview/instagram/rules
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/instagram/rules:
    post:
      tags:
        - Instagram
      summary: Create the next-post Instagram DM rule
      description: >
        Creates the account's **next-post** automation rule. When a comment on a
        post matches one of the rule's `keywords`,

        DocuRise replies to the comment and sends the commenter a DM.


        By default the rule is created in the `waiting_for_post` status: it is
        not

        bound to any post yet and **activates automatically on the account's
        next

        new post**, after which it behaves like any other rule. Send

        `"status": "paused"` to create it **paused** instead: it is saved and

        holds the account's one next-post slot, but nothing is sent until you

        resume it with `PATCH /api/preview/instagram/rules/{id}` and

        `{ "status": "active" }`. A paused rule cannot carry

        `schedule.batchSendAt` (`422 BATCH_RULE_NOT_PAUSABLE`, nothing is
        created).


        Text fields (`keywords`, reply `variants`, `greeting` and `dm` messages
        and button texts,

        button and card URLs, `card.subtitle`) must not contain a NUL (U+0000)
        character: it is a

        `400 VALIDATION_ERROR` naming the field, and nothing is created.


        Requires the `instagram:write` scope and a connected Instagram account.


        **One next-post rule per account.** While one exists (still waiting for

        its post, or paused), another create is rejected with

        `NEXT_POST_RULE_EXISTS`; `error.details.ruleId` identifies it so you can

        edit it with `PATCH /api/preview/instagram/rules/{id}`, or delete it in
        the dashboard, and retry.


        `target` and `type` are optional: leaving them out is the same as
        sending

        `{ "type": "next_post" }` and `"standard"`, the only values accepted
        today.


        Unknown fields are rejected with `VALIDATION_ERROR` rather than ignored.
      operationId: createInstagramRule
      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/CreateInstagramRuleRequest'
            examples:
              minimal:
                summary: Reply to every comment with one DM
                value:
                  commentReply:
                    variants:
                      - Sent you a DM!
                  dm:
                    message: Here is the guide you asked for
              paused:
                summary: Save the rule paused, to review before it goes live
                value:
                  status: paused
                  commentReply:
                    variants:
                      - Sent you a DM!
                  dm:
                    message: Here is the guide you asked for
              full:
                summary: Keyword-triggered, follow-gated DM with a link button
                value:
                  target:
                    type: next_post
                  type: standard
                  keywords:
                    - guide
                    - link
                  commentReply:
                    enabled: true
                    variants:
                      - Sent you a DM!
                      - Check your DMs 📩
                  greeting:
                    enabled: true
                    message: Tap below to get the guide
                    buttonText: Send it
                  dm:
                    message: Here is the guide 👇
                    requireFollow: true
                    nonFollowerMessage: Follow us first, then tap again
                    nonFollowerButtonText: I followed
                    buttons:
                      - text: Open guide
                        url: https://docurise.io/v/my-guide
                  schedule:
                    startAt: '2026-10-01T00:00:00Z'
                    endAt: '2026-10-31T23:59:59Z'
      responses:
        '201':
          description: >-
            Rule created. Its status is `waiting_for_post` (it waits for the
            next post), or `paused` if you asked for that.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateInstagramRuleResponse'
        '400':
          description: |
            `VALIDATION_ERROR` (the body failed validation — `message` is
            prefixed with the offending field path, e.g. `dm.message: …`),
            `IDEMPOTENCY_KEY_REQUIRED`, `IDEMPOTENCY_KEY_TOO_LONG`, or
            `INVALID_JSON`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: |
            `PLAN_REQUIRED`.
            `INSUFFICIENT_SCOPE` — the key lacks `instagram:write`.
            `PAST_DUE_RESTRICTED`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          $ref: '#/components/responses/NotOnAllowlist'
        '409':
          description: |
            `INSTAGRAM_NOT_CONNECTED` — no connected Instagram account; connect
            one in the dashboard first.
            `NEXT_POST_RULE_EXISTS` — a next-post rule already exists;
            `error.details.ruleId` is its id.
            `IDEMPOTENCY_KEY_CONFLICT` / `IDEMPOTENCY_KEY_IN_PROGRESS` — see
            "Idempotency" above.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                nextPostRuleExists:
                  value:
                    error:
                      code: NEXT_POST_RULE_EXISTS
                      message: >-
                        This account already has a next-post rule. Delete it or
                        edit it in the dashboard
                      retryable: false
                      details:
                        ruleId: 7f1c0f7e-3c5e-4b1a-9d34-2f0a5b6c8d90
        '422':
          description: >
            `BATCH_RULE_NOT_PAUSABLE` — `status` is `paused` together with

            `schedule.batchSendAt`. A batch send needs a live rule. Nothing was

            created. The same code is `409` on `PATCH` when the rule already has
            a

            batch send.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          description: >-
            `INTERNAL_ERROR`: unexpected failure. Retryable; the cause is not
            returned.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '502':
          description: |
            `BATCH_SCHEDULE_FAILED` — the batch send (`schedule.batchSendAt`)
            could not be scheduled, and the rule was rolled back. Always
            retryable with the same `Idempotency-Key`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '503':
          $ref: '#/components/responses/Unavailable'
      security:
        - apiKey: []
components:
  schemas:
    CreateInstagramRuleRequest:
      type: object
      required:
        - commentReply
        - dm
      properties:
        status:
          type: string
          enum:
            - active
            - paused
          default: active
          description: |
            `active` (the default) arms the rule, so it waits for the next post.
            `paused` saves it paused: nothing is sent until you resume it with
            `PATCH` and `{ "status": "active" }`. Cannot be combined with
            `schedule.batchSendAt` (`422 BATCH_RULE_NOT_PAUSABLE`).
        target:
          type: object
          required:
            - type
          description: >-
            What the rule applies to. Optional; only the account's next new post
            is supported today, so leaving it out is the same as `{ "type":
            "next_post" }`.
          properties:
            type:
              type: string
              enum:
                - next_post
              description: The kind of target. Only `next_post` is supported today.
          additionalProperties: false
          default:
            type: next_post
        type:
          type: string
          enum:
            - standard
          default: standard
          description: >-
            Rule type. Optional. Only `standard` (keyword comment → DM) can be
            created through the API today.
        keywords:
          type: array
          maxItems: 20
          items:
            type: string
            minLength: 1
            maxLength: 50
          default: []
          description: >
            A comment triggers the rule when it **contains** any keyword —
            matched as a

            case-insensitive substring, so `guide` also matches "Guide please!".
            Empty

            means every comment triggers it; if a post has both a keyword rule
            and an

            all-comments rule, the keyword rule wins when it matches.
        commentReply:
          type: object
          description: >
            Public reply DocuRise posts under the matching comment; one variant
            is

            picked at random per comment. Required — send `{ "enabled": false }`
            to

            reply to nobody publicly.
          properties:
            enabled:
              type: boolean
              default: true
              description: >-
                Whether DocuRise replies publicly under a matching comment.
                Defaults to `true`.
            variants:
              type: array
              maxItems: 10
              items:
                type: string
                minLength: 1
                maxLength: 500
              description: Required (at least one) when `enabled` is true.
          additionalProperties: false
        greeting:
          type: object
          description: |
            The first DM, a message with one tap-to-continue button. The
            commenter's tap opens Instagram's messaging window and releases the
            main DM. When `enabled` is false the main DM is sent directly as a
            private reply instead. Blank `message` / `buttonText` use
            DocuRise's default wording.
          properties:
            enabled:
              type: boolean
              default: true
              description: >-
                Whether the greeting DM with its tap-to-continue button is sent
                first. When `false`, the main DM is sent directly as a private
                reply. Defaults to `true`.
            message:
              type: string
              maxLength: 1000
              description: >-
                Text of the greeting DM, up to 1000 characters. Blank uses
                DocuRise's default wording in the rule's `locale`.
            buttonText:
              type: string
              maxLength: 20
              description: >-
                Label of the tap-to-continue button, up to 20 characters. Blank
                uses DocuRise's default wording.
          additionalProperties: false
        dm:
          type: object
          required:
            - message
          description: The main DM the commenter receives.
          properties:
            message:
              type: string
              minLength: 1
              maxLength: 1000
              description: >
                At most 1000 characters — or **80 when `card` sets an image or
                subtitle**,

                because Instagram sends a rich card's text as the card title.
                Leading and

                trailing whitespace is trimmed; it must not be blank.
            requireFollow:
              type: boolean
              default: false
              description: |
                Only send `message` to people who follow the account; everyone
                else gets `nonFollowerMessage`. Requires `nonFollowerMessage`,
                the greeting step (`greeting.enabled`), and no
                `schedule.batchSendAt`.
            nonFollowerMessage:
              type: string
              maxLength: 1000
              description: >-
                DM sent to commenters who do not follow the account, up to 1000
                characters. Required when `requireFollow` is `true`.
            nonFollowerButtonText:
              type: string
              maxLength: 20
              description: Label of the button non-followers tap once they have followed.
            buttons:
              type: array
              maxItems: 3
              items:
                $ref: '#/components/schemas/InstagramButton'
              default: []
              description: Up to 3 link buttons shown under the DM.
            card:
              type: object
              description: >
                Sends the DM as a rich card with an image and/or subtitle. While
                either is

                set, `dm.message` is limited to 80 characters.
              properties:
                imageUrl:
                  type: string
                  maxLength: 2048
                  description: >-
                    https image URL. A bare host is upgraded to `https://`;
                    `http://` is rejected.
                subtitle:
                  type: string
                  maxLength: 80
                  description: >-
                    Line breaks are replaced with spaces before the length is
                    checked.
              additionalProperties: false
          additionalProperties: false
        schedule:
          type: object
          description: >
            Optional timing. Either an operating window (`startAt` / `endAt`) or

            a single batch send (`batchSendAt`), never both. Omit for "always
            on".

            For a next-post rule the window is applied when the rule activates
            on

            the new post; a window that has already ended by then is dropped and

            the rule simply goes live.
          properties:
            startAt:
              type: string
              format: date-time
              description: >
                RFC 3339 / ISO 8601 with an offset: `Z` or a numeric offset
                (`2026-10-01T00:00:00Z`,

                `2026-10-01T09:00:00+09:00`). Normalized to UTC; responses
                always report UTC. A

                timestamp with no offset (`2026-10-01T09:00:00`) is rejected.
            endAt:
              type: string
              format: date-time
              description: >-
                Same format as `startAt`. Must be after `startAt` (compared as
                instants).
            batchSendAt:
              type: string
              format: date-time
              description: >
                Batch mode: collect every matching commenter and send all DMs at

                this moment. Same format as `startAt`; must be in the future and
                within 6 days.
          additionalProperties: false
        locale:
          type: string
          enum:
            - en
            - ko
          description: >-
            Language of DocuRise's default wording (greeting and button text
            left blank). Defaults to the account's language.
      additionalProperties: false
    CreateInstagramRuleResponse:
      type: object
      required:
        - id
        - url
        - status
        - type
        - target
      properties:
        id:
          type: string
          format: uuid
          description: The new rule's id.
        url:
          type: string
          format: uri
          description: Link to the rule in the dashboard editor.
        status:
          type: string
          enum:
            - waiting_for_post
            - paused
          description: >-
            `waiting_for_post` for an armed rule (the default), `paused` when
            you created it with `status: paused`.
        type:
          type: string
          description: 'The rule type: `standard` for a rule created through the API.'
        target:
          type: object
          properties:
            type:
              type: string
              description: >-
                The kind of target: `next_post` for a rule created through the
                API.
          description: What the rule applies to.
      example:
        id: 7f1c0f7e-3c5e-4b1a-9d34-2f0a5b6c8d90
        url: >-
          https://docurise.io/dashboard/instagram/rules/7f1c0f7e-3c5e-4b1a-9d34-2f0a5b6c8d90
        status: waiting_for_post
        type: standard
        target:
          type: next_post
    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`.
    InstagramButton:
      type: object
      required:
        - url
      properties:
        text:
          type: string
          maxLength: 20
          default: ''
          description: Button label.
        url:
          type: string
          maxLength: 2048
          description: >
            https link. A bare host (`example.com`) is upgraded to `https://`;
            `http://` and other schemes are rejected.

            To link one of your DocuRise documents, use its share `url` (the one
            returned when you create a

            document): link-click analytics recognize it as a document button.
      additionalProperties: false
  responses:
    Unauthorized:
      description: 'Missing, malformed, or revoked API key (`code: UNAUTHORIZED`).'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    NotOnAllowlist:
      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'
    RateLimited:
      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'
    Unavailable:
      description: |
        A server-side dependency is temporarily down (`RATE_LIMIT_UNAVAILABLE`
        or `SERVICE_UNAVAILABLE`). Not a problem with the request or key;
        always retryable — honor `retryAfterSeconds` when present. The group
        endpoints (`POST /segments`, `POST /segments/preview`,
        `GET /segments/{id}`) also return it when a very heavy rule could not be
        counted within about 10 seconds: simplify the rule or retry.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
  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.