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

> Create one group from a rule; an Idempotency-Key makes retries safe



## OpenAPI

````yaml api-reference/openapi.yaml POST /api/preview/segments
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/segments:
    post:
      tags:
        - Audiences
      summary: Create a group
      description: >
        Creates one group from a rule and returns it. Requires the

        `segments:write` scope. A key created before this scope existed does not

        have it: add it with **Edit scopes** under Settings → Developers (the
        key

        itself does not change).


        The body is a `name` and a `filter`, the rule that decides who is in the

        group. The rule is built from conditions in three lists: `conditions`

        (joined by `operator`, `AND` or `OR`), `requiredConditions` (always

        AND-ed in) and `excludeConditions` (a contact matching any of them is

        removed). A rule needs at least one condition across the three lists. A
        few

        condition shapes are refused in some lists (`422
        CONDITION_NOT_SUPPORTED`,

        see [Group conditions](/api-reference/group-conditions)). The

        condition types, their keys and values, and where to find the ids a

        condition needs are on [Group
        conditions](/api-reference/group-conditions).

        To see who a rule matches before saving it, use

        `POST /api/preview/segments/preview`: it validates the rule the same
        way.


        A group counts everyone who matches the rule except unsubscribed

        contacts, who are never members. Names are not

        unique; two groups may share a name, so keep the returned `id`. The
        `name`

        in the response is the stored one: trimmed and Unicode-normalized (NFC).


        Creating a group sends nothing and starts no automation. `memberCount`
        in the response is the exact number of contacts the rule matches at the
        moment of creation. `contactCount` in `GET /api/preview/segments` can
        lag for a moment.


        An account can create up to 100 groups through the API. At the limit the

        call returns `422 GROUP_LIMIT_REACHED` with `details.limit`.


        **`Idempotency-Key` is required.** The same key with the same body
        returns

        the first response, so a retry after a timeout never creates a second

        group. A refused request is remembered with its key: sending the same
        key

        and body again returns the same refusal, even after you have fixed the
        cause

        (for example created the missing tag, or deleted a group to get under

        `GROUP_LIMIT_REACHED`). After fixing the cause, send a new
        `Idempotency-Key`.
      operationId: createSegment
      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/CreateSegmentRequest'
            example:
              name: Newsletter readers, still active
              filter:
                operator: AND
                conditions:
                  - field: tag
                    operator: has
                    value: newsletter
                requiredConditions:
                  - field: consent
                    operator: active
                excludeConditions:
                  - field: last_active
                    operator: inactive_for_days
                    value: 90
      responses:
        '201':
          description: The group was created. `memberCount` is the live count at creation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SegmentDetail'
              example:
                id: 3d2b8a64-5e0f-4c71-b9a2-1f7e6c4d8a05
                name: Newsletter readers, still active
                filter:
                  operator: AND
                  conditions:
                    - field: tag
                      operator: has
                      value: newsletter
                  requiredConditions:
                    - field: consent
                      operator: active
                  excludeConditions:
                    - field: last_active
                      operator: inactive_for_days
                      value: 90
                memberCount: 124
                createdAt: '2026-10-22T03:14:15.926Z'
        '400':
          description: >
            `INVALID_JSON` (the body is not JSON), `IDEMPOTENCY_KEY_REQUIRED`,

            `IDEMPOTENCY_KEY_TOO_LONG` or `VALIDATION_ERROR` (a bad `name` or

            `filter`; `message` starts with the path of the bad part, for
            example

            `name:` or `filter.conditions.2.value:`. Causes include a missing or

            empty `name`, a name over 100 characters, a NUL character, an
            unknown

            key in the body, the rule or a condition, a wrong value or type, a
            list

            of more than 20 conditions, an id that is not a UUID, and a
            combination

            of keys a condition does not allow).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error:
                  code: VALIDATION_ERROR
                  message: >-
                    filter.conditions.0.value: Number must be less than or equal
                    to 365
                  retryable: false
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: >-
            `PAST_DUE_RESTRICTED`, `PLAN_REQUIRED` or `INSUFFICIENT_SCOPE` (the
            key lacks `segments:write`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          $ref: '#/components/responses/NotOnAllowlist'
        '409':
          description: >
            `IDEMPOTENCY_KEY_CONFLICT` (not retryable: the same key was already
            used

            with a different 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: >
            The request is well-formed but the rule cannot be used. Nothing was

            created. None is retryable as is.

            `EMPTY_FILTER`: the rule has no condition in any of its three lists.

            `CONDITION_NOT_SUPPORTED`: a condition type the API does not accept
            yet

            (`custom_field`), or a condition shape that gives a wrong answer for
            some

            contacts in the list it is in (see Group conditions);

            `details` is `{ field, path }`, plus `{ operator, invertTo }` for
            the second

            kind (`invertTo` is left out when there is no equivalent operator).

            `REFERENCE_NOT_FOUND`: a condition carries an id that is not in your

            account (unknown, deleted, in the trash or someone else's, all

            indistinguishable); `details` is `{ path, kind }`, with `kind` one
            of

            `document`, `product`, `tier`, `cta`, `folder`, `broadcast` or
            `step`.

            `TAG_NOT_FOUND`: a `tag` condition names a tag your account does not

            have (names are matched exactly, case included); `details` is

            `{ tag }`. Look names up with `GET /api/preview/tags`.

            `GROUP_REFERENCE_NOT_FOUND`: a `group_membership` condition refers
            to a

            group that is not in your account; `details` is `{ groupIds }`.

            `GROUP_LIMIT_REACHED`: the account is at the limit of 100 groups;

            `details` is `{ limit }`. Delete groups you no longer use in the

            dashboard, then retry with a new `Idempotency-Key`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                emptyFilter:
                  value:
                    error:
                      code: EMPTY_FILTER
                      message: >-
                        The filter has no conditions. Add at least one
                        condition, or the group would match every contact
                      retryable: false
                conditionNotSupported:
                  value:
                    error:
                      code: CONDITION_NOT_SUPPORTED
                      message: >-
                        The custom_field condition is not supported through the
                        API yet. Remove it from the filter
                      retryable: false
                      details:
                        field: custom_field
                        path: filter.conditions.1
                referenceNotFound:
                  value:
                    error:
                      code: REFERENCE_NOT_FOUND
                      message: >-
                        A condition refers to something that is not in your
                        account
                      retryable: false
                      details:
                        path: filter.conditions.0.documentId
                        kind: document
                tagNotFound:
                  value:
                    error:
                      code: TAG_NOT_FOUND
                      message: A tag condition names a tag that is not in your account
                      retryable: false
                      details:
                        tag: newsletter
                groupReferenceNotFound:
                  value:
                    error:
                      code: GROUP_REFERENCE_NOT_FOUND
                      message: >-
                        A group condition refers to a group that is not in your
                        account
                      retryable: false
                      details:
                        groupIds:
                          - 9c1e4a7b-2d3f-4e58-a6b0-5f8d7c3e1a24
                groupLimitReached:
                  value:
                    error:
                      code: GROUP_LIMIT_REACHED
                      message: This account has reached its group limit
                      retryable: false
                      details:
                        limit: 100
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          description: >-
            `INTERNAL_ERROR` (retryable). Something failed on our side; the
            cause is not echoed. Retry with the same `Idempotency-Key`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '503':
          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.

            `SERVICE_UNAVAILABLE` is also returned when the service was too busy
            to

            start the create: nothing was created, and retrying with the same

            `Idempotency-Key` is safe. The group endpoints 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'
      security:
        - apiKey: []
      x-codeSamples:
        - lang: bash
          label: curl
          source: |
            curl -X POST 'https://docurise.io/api/preview/segments' \
              -H 'Authorization: Bearer dr_live_<keyId>_<secret>' \
              -H 'Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000' \
              -H 'Content-Type: application/json' \
              -d '{
                "name": "Newsletter readers, still active",
                "filter": {
                  "operator": "AND",
                  "conditions": [{ "field": "tag", "operator": "has", "value": "newsletter" }],
                  "requiredConditions": [{ "field": "consent", "operator": "active" }],
                  "excludeConditions": [{ "field": "last_active", "operator": "inactive_for_days", "value": 90 }]
                }
              }'
components:
  schemas:
    CreateSegmentRequest:
      type: object
      required:
        - name
        - filter
      additionalProperties: false
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 100
          description: >-
            The group name. It is trimmed and Unicode-normalized (NFC), then
            must be 1-100 characters and must not contain a NUL character. Names
            are not unique.
          example: Newsletter readers, still active
        filter:
          $ref: '#/components/schemas/GroupFilter'
    SegmentDetail:
      type: object
      required:
        - id
        - name
        - filter
        - memberCount
        - createdAt
      description: A group with its rule and its live member count. Never any member data.
      properties:
        id:
          type: string
          format: uuid
          description: The group id.
        name:
          type: string
          description: The group name.
        filter:
          $ref: '#/components/schemas/StoredGroupFilter'
        memberCount:
          type: integer
          minimum: 0
          description: >-
            The live number of contacts in the group now (never unsubscribed
            contacts). A count only; the contacts themselves are never returned.
        createdAt:
          type: string
          format: date-time
          description: When the group was created, RFC 3339 in UTC.
    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`.
    GroupFilter:
      type: object
      required:
        - operator
        - conditions
      additionalProperties: false
      description: >
        The rule that decides who is in a group. A contact is in the group when
        it

        matches `conditions` (joined by `operator`), matches every condition in

        `requiredConditions`, and matches none of `excludeConditions`. Each list
        holds

        at most 20 conditions, and at least one condition is needed across the
        three

        lists (otherwise `422 EMPTY_FILTER`). Unsubscribed contacts are never
        members.

        See [Group conditions](/api-reference/group-conditions).
      properties:
        operator:
          type: string
          enum:
            - AND
            - OR
          description: >-
            How the conditions in `conditions` are joined, `AND` (all of them)
            or `OR` (any of them).
        conditions:
          type: array
          maxItems: 20
          items:
            $ref: '#/components/schemas/GroupCondition'
          description: >-
            The main list of conditions. May be empty when `requiredConditions`
            or `excludeConditions` has some.
        requiredConditions:
          type: array
          maxItems: 20
          items:
            $ref: '#/components/schemas/GroupCondition'
          description: >-
            Conditions every member must also match. Always AND-ed in, whatever
            `operator` is.
        excludeConditions:
          type: array
          maxItems: 20
          items:
            $ref: '#/components/schemas/GroupCondition'
          description: >-
            Conditions that remove contacts. A contact matching any one of them
            is left out. A few condition shapes are refused here (`422
            CONDITION_NOT_SUPPORTED`); see [Group
            conditions](/api-reference/group-conditions).
    StoredGroupFilter:
      type: object
      additionalProperties: true
      description: >
        A saved rule exactly as it is stored, which is what `GET
        /api/preview/segments/{id}`

        returns. Unlike `GroupFilter` (what you may send) it has no limit on the
        length of a

        list, and its conditions can be of any type (`StoredGroupCondition`),
        because a saved group is not held to the API's rules. A group

        stored without a rule comes back as `{}`.
      properties:
        operator:
          type: string
          enum:
            - AND
            - OR
          description: >-
            How the conditions in `conditions` are joined, `AND` (all of them)
            or `OR` (any of them).
        conditions:
          type: array
          items:
            $ref: '#/components/schemas/StoredGroupCondition'
          description: The main list of conditions.
        requiredConditions:
          type: array
          items:
            $ref: '#/components/schemas/StoredGroupCondition'
          description: Conditions every member must also match.
        excludeConditions:
          type: array
          items:
            $ref: '#/components/schemas/StoredGroupCondition'
          description: Conditions that remove contacts.
    GroupCondition:
      type: object
      required:
        - field
      additionalProperties: true
      description: >
        One condition of a rule: an object whose required `field` names the
        condition

        type, plus the keys that type takes (for example `operator` and
        `value`). The

        20 types, their keys and allowed values, and where to find the ids they
        need

        are on [Group conditions](/api-reference/group-conditions); this schema
        lists

        only `field` so the two cannot disagree. The server validates every
        condition

        strictly: an unknown key, a wrong value or a combination of keys the
        type does

        not allow is a `400 VALIDATION_ERROR` naming the path. A `custom_field`

        condition is refused with `422 CONDITION_NOT_SUPPORTED`, and so are the
        few

        condition shapes that give a wrong answer for some contacts in the list
        they

        are in (see Group conditions).
      properties:
        field:
          type: string
          enum:
            - tag
            - document_view
            - consent
            - cta_click
            - email_open
            - email_link_click
            - has_phone
            - form_submission
            - payment_status
            - first_seen
            - last_active
            - group_membership
            - tier_access
            - days_since_purchase
            - repeat_bounce
            - course_last_access
            - kakao_reachable
            - course_progress
            - location
            - checkout_abandoned
          description: The condition type.
      example:
        field: tag
        operator: has
        value: newsletter
    StoredGroupCondition:
      type: object
      required:
        - field
      additionalProperties: true
      description: >
        One condition of a saved rule, as it is stored. Unlike `GroupCondition`
        (what you

        may send), `field` can be any condition type, including `custom_field`,
        which the API cannot create. The keys of each type are on

        [Group conditions](/api-reference/group-conditions).
      properties:
        field:
          type: string
          description: >-
            The condition type. One of the 20 types on Group conditions, or
            `custom_field`.
      example:
        field: custom_field
        questionId: q9f2k1x7ab
        operator: exists
  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'
  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.