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

# List top documents

> Rank your own documents by views, unique visitors or leads over a UTC date range



## OpenAPI

````yaml api-reference/openapi.yaml GET /api/preview/analytics/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/analytics/documents:
    get:
      tags:
        - Analytics
      summary: List your top documents
      description: >
        Ranks **your own** documents by views, unique visitors or leads over a

        date range and returns the top few. Each entry carries the same four

        numbers as `GET /api/preview/documents/{id}/analytics` for

        that document and range. Needs the `analytics:read` scope. Only

        documents you own are ever included; there is no way to read another

        account's documents.


        **Range.** `from` and `to` are both required RFC 3339 datetimes with

        `Z` or a numeric offset (for example `2026-10-02T09:00:00+09:00`); they

        are converted to UTC. A datetime with no offset is rejected. The range

        is **UTC, start-inclusive and end-exclusive**: `[from, to)`. A row at

        exactly `to` is not counted. `from` may equal `to` (an empty range, so

        an empty list); `from` after `to` is rejected. The range may span at

        most **366 days**. The response echoes the range as UTC.


        **Ranking.** `sort` picks the metric: `views` (the default),

        `uniqueVisitors` or `leads`. Entries are ordered by that metric from

        highest to lowest; documents with the same value are ordered by

        `documentId` (ascending), so the order is stable between calls.

        `conversionRate` is not a sort option.


        **Which documents are listed.** A document with **no views and no

        leads** in the range is not listed, so the list can be shorter than

        `limit` (or empty), and a document that exists but had no activity is

        never padded in. A document with leads but no counted visits in the

        range is listed (`views: 0`, `uniqueVisitors: 0`, `conversionRate: 0`).


        **Limit.** `limit` defaults to **10**. A value above **100** is clamped

        to 100 (not an error); `0`, a negative number or a non-integer is a

        `400`. This is a ranking, not a listing: there is **no cursor and no

        next page**. To see more, narrow the range or request a different sort.


        **Definitions** (identical to `GET
        /api/preview/documents/{id}/analytics`).

        - `views`: visits (viewing sessions) that started in the range. A
          viewer who opens the document three times is three views.
        - `uniqueVisitors`: distinct viewers across those visits.

        - `leads`: distinct leads (one per email address, or per phone number
          for phone-only leads) first seen in the range. Anonymous visitors who
          never gave an email or phone are not leads.
        - `conversionRate`: `leads / uniqueVisitors`, rounded to 4 decimals
          (`0.5` = 50%). It is `0` when there are no visitors in the range. It
          is not capped at 1: leads and visits are dated separately, so a lead
          whose visit is outside the range can push it above 1.
        - Your own visits (opening your own document while signed in) are
          always excluded from `views`, `uniqueVisitors` and `leads`.
        - No bot filtering is applied.

        - `kind` is the document type: `notion`, `pdf`, `upload`, `post` or
          `form`. Form documents are counted from visit sessions like every
          other kind; form visit tracking started on 2026-06-17.

        **Freshness.** Numbers are computed on request, not snapshotted, so a

        visit or lead recorded after you read a range changes the next read of

        that same range.


        Counts only: the response never contains individual viewers, email

        addresses or lead rows. Unknown or repeated query parameters

        (including `cursor`) are rejected with `400 VALIDATION_ERROR`.
      operationId: listTopDocuments
      parameters:
        - name: from
          in: query
          required: true
          description: >-
            Start of the range, inclusive. RFC 3339 with `Z` or a numeric
            offset.
          schema:
            type: string
            format: date-time
          example: '2026-10-01T00:00:00Z'
        - name: to
          in: query
          required: true
          description: >-
            End of the range, exclusive. RFC 3339 with `Z` or a numeric offset.
            At most 366 days after `from`.
          schema:
            type: string
            format: date-time
          example: '2026-10-08T00:00:00Z'
        - name: sort
          in: query
          required: false
          description: >-
            The metric to rank by, highest first. Ties are ordered by
            `documentId` ascending.
          schema:
            type: string
            enum:
              - views
              - uniqueVisitors
              - leads
            default: views
        - name: limit
          in: query
          required: false
          description: >-
            How many documents to return. Default 10; values above 100 are
            clamped to 100. There is no paging.
          schema:
            type: integer
            minimum: 1
            default: 10
      responses:
        '200':
          description: The ranked documents for the range (possibly an empty `data` array).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TopDocumentsResponse'
              example:
                from: '2026-10-01T00:00:00.000Z'
                to: '2026-10-08T00:00:00.000Z'
                sort: leads
                data:
                  - documentId: 3b0c8a52-6f1e-4d0a-9a1c-0d8f2f6a7b11
                    title: Q4 pricing deck
                    kind: notion
                    views: 128
                    uniqueVisitors: 74
                    leads: 19
                    conversionRate: 0.2568
                  - documentId: 8d1f6c42-91ab-4c7e-b2f3-5a7e0c9d4e22
                    title: Onboarding checklist
                    kind: pdf
                    views: 311
                    uniqueVisitors: 205
                    leads: 12
                    conversionRate: 0.0585
        '400':
          description: |
            `VALIDATION_ERROR`: `from` or `to` missing, not an RFC 3339
            datetime with `Z` or an offset, `from` after `to`, a range longer
            than 366 days, a `sort` that is not `views`, `uniqueVisitors` or
            `leads`, a `limit` that is not a positive integer, or an unknown or
            repeated query parameter (including `cursor`). Never retryable as
            sent.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: |
            One of `INSUFFICIENT_SCOPE` (the key lacks `analytics:read`),
            `PLAN_REQUIRED` or
            `PAST_DUE_RESTRICTED`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          $ref: '#/components/responses/NotOnAllowlist'
        '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'
        '503':
          $ref: '#/components/responses/Unavailable'
      security:
        - apiKey: []
      x-codeSamples:
        - lang: bash
          label: curl
          source: |
            curl -G "https://docurise.io/api/preview/analytics/documents" \
              -H "Authorization: Bearer $DOCURISE_API_KEY" \
              --data-urlencode "from=2026-10-01T00:00:00Z" \
              --data-urlencode "to=2026-10-08T00:00:00Z" \
              --data-urlencode "sort=leads" \
              --data-urlencode "limit=5"
components:
  schemas:
    TopDocumentsResponse:
      type: object
      required:
        - from
        - to
        - sort
        - data
      properties:
        from:
          type: string
          format: date-time
          description: Start of the range as requested, in UTC (inclusive).
        to:
          type: string
          format: date-time
          description: End of the range as requested, in UTC (exclusive).
        sort:
          type: string
          enum:
            - views
            - uniqueVisitors
            - leads
          description: >-
            The metric the list is ranked by (the default `views` when `sort`
            was not given).
        data:
          type: array
          description: >-
            Highest first, ties by `documentId` ascending. At most `limit`
            entries; no next page.
          items:
            $ref: '#/components/schemas/TopDocument'
    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`.
    TopDocument:
      type: object
      required:
        - documentId
        - title
        - kind
        - views
        - uniqueVisitors
        - leads
        - conversionRate
      properties:
        documentId:
          type: string
          format: uuid
          description: The document's id.
        title:
          type: string
          description: The document's title.
        kind:
          type: string
          enum:
            - notion
            - pdf
            - upload
            - post
            - form
          description: The document type.
        views:
          type: integer
          description: Visits that started in the range, excluding your own.
        uniqueVisitors:
          type: integer
          description: Distinct viewers across those visits.
        leads:
          type: integer
          description: >-
            Distinct leads (by email, or phone for phone-only leads) first seen
            in the range.
        conversionRate:
          type: number
          description: >-
            leads / uniqueVisitors, 4 decimals; 0 with no visitors; can exceed
            1.
  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.