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

> ## Agent Instructions
> - Base URLs: models `https://api.aisa.one/v1` (Gemini GenerateContent: `https://api.aisa.one/v1beta`); data APIs `https://api.aisa.one/apis/v1`. The `servers` of each operation in https://aisa.one/openapi.yaml is authoritative.
> - Auth: `Authorization: Bearer $AISA_API_KEY`. Never print, log, or commit the key.
> - MCP (preferred for agents): `https://mcp.aisa.one/mcp`, signed in with OAuth, so there is no key to paste.
> - Agent Skill: `npx skills add AIsa-team/agent-skills --skill aisa`. Fallback with the AIsa CLI: `aisa skills install aisa`.
> - Spending: docs, https://aisa.one/models, https://aisa.one/api, `/v1/models`, and MCP `search`, `list_categories`, `get_details`, `batch_quote`, and the `account` operation are free. Quote the exact calls before running them (MCP `batch_quote`, CLI `aisa quote --json`). If the quote reports `auto_execute_eligible: true`, run it unless the user set a tighter limit or asked only for a quote. Otherwise, tell the user in one message the planned calls, the quoted price, and a total USD budget for the task, then wait for approval. Direct HTTP calls follow the stricter rule at https://aisa.one/docs/get-started/cli.md#direct-http-fallback. Do not make paid calls to test availability. Full rule: https://aisa.one/llms.txt

# Analytics changed since a cursor

> Analytics. Read only; needs social:read. Ids are yours: accounts acc_..., profiles prf_..., posts pst_...; every other object id is checked against your accounts before the call. Without profileId or accountId it reads your default profile.

Cursor feed of the analytics snapshots that CHANGED, across every account you can read, in one paginated stream. Built for integrations that would otherwise call `GET /apis/v1/social/analytics` once per connected account.

Each page carries changes from many accounts at once, so your call count scales with how much actually changed rather than with how many accounts you have.

Measured against a fleet of roughly 1,600 connected accounts: about 1,599 per-account analytics calls an hour became about 205 delta calls an hour, a 7.8x reduction.

### Bootstrap once, then stay in sync

1. Take the cursor FIRST: call this endpoint with NO `cursor`. It answers with an empty `data` array plus the feed's current position in `nextCursor`.
2. Load your baseline from `GET /apis/v1/social/analytics`, the historical endpoint. This one is a rolling 7-day change log and cannot replay history.
3. Resume from the cursor you took before the baseline.

Taking the cursor afterwards instead drops every change that lands while the baseline is loading: it is in neither the row you already read nor the feed you resume behind it.

The overlap this order creates is safe. Metrics are absolute values rather than increments, so draining it leaves every post on its newest value.

`nextCursor` is present on every response, empty pages included, so you always have something to advance with.

### Ordering

Entries come back oldest first, in the order the feed received them. That order is NOT `syncedAt`. `syncedAt` is stamped when an account's sync cycle started, and a slow cycle writes its rows after a faster cycle that started later, so `syncedAt` can go backwards between consecutive entries.

Do not sort, filter or resume on `syncedAt`. The cursor is the only stable position, and it is opaque: pass it back verbatim, and do not parse, construct or compare cursors.

### `hasMore: false` does not mean the feed ended

This stream has no end and `nextCursor` is never null.

- `hasMore: true`: more changes are already waiting, so call again straight away.
- `hasMore: false`: you are caught up. Keep the cursor and poll again on your normal interval.

### The newest changes settle before they are served

The feed deliberately holds back its last few seconds of writes, so that a row can never become visible behind a cursor you have already advanced past.

A read issued the instant an `analytics.synced` webhook lands will therefore often return an empty page for that account. Do not read an empty page as "nothing changed": poll again with the SAME cursor you last used rather than advancing.

### Repeats inside one instant

A sync cycle occasionally records the same post twice at the same feed position. When that happens the feed delivers one of those rows, not both.

Measured over a day of production traffic, about 1.3% of rows fall in such a group and 99.4% of those groups are identical rows, so this is far more often deduplication than loss.

Metrics are absolute values rather than increments, so a later entry for the same post supersedes an earlier one.

### Retention is 7 days

Changes older than that leave the feed. A cursor older than 6 days is rejected with a `400` (a day of margin, because expiry is lazy).

Recover the same way you bootstrapped:

1. Take a fresh cursor from a call to this endpoint with no `cursor`.
2. Re-load from `GET /apis/v1/social/analytics`.
3. Resume from that cursor.

A consumer that polls at least daily never reaches this.

### Webhook and access

Pairs with the `analytics.synced` webhook, so changes can be read on notification instead of on a timer. That event carries no cursor of its own: keep using the `nextCursor` this endpoint gave you.

Requires the same analytics access as `GET /apis/v1/social/analytics`, and shares the stricter per-second rate-limit window applied to analytics endpoints.

<!-- aisa-pricing:start -->
当前未提供请求参考价格；请使用接口支持的报价功能确认费用。 最终费用以账户适用价格及实际结算为准。
<!-- aisa-pricing:end -->

## 请求规范

* **方法与路径:** GET /apis/v1/social/analytics/delta
* **可用状态:** enabled
* **请求 URL 模板:** `https://api.aisa.one/apis/v1/social/analytics/delta`
* **cursor:** query; 可选
* **limit:** query; 可选
* **platform:** query; 可选
* **profileId:** query; 可选
* **Runtime 事实版本:** 99acb5bddbedc8d3352e7c4fef86c3e6d8852c14772bcd18dbcf415237a642bd

[完整 OpenAPI 定义](https://aisa.one/docs/reference-contracts/zh/aisa-social.json)

有效请求与价格声明（包括认证和引用的 schema）：

```json theme={null}
{
  "operationId": "get_aisa_social_apis_v1_social_analytics_delta",
  "method": "GET",
  "path": "/apis/v1/social/analytics/delta",
  "servers": [
    {
      "url": "https://api.aisa.one"
    }
  ],
  "parameters": [
    {
      "description": "Opaque cursor from a previous response's `nextCursor`. Omit it to start from\nnow: the response is then an empty page carrying the feed's current position.\nRejected with a `400` when malformed, or when older than the retention window.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
        "type": "string"
      }
    },
    {
      "description": "Page size. Out-of-range values are a 400, never a silent clamp.",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
        "default": 50,
        "maximum": 100,
        "minimum": 1,
        "type": "integer"
      }
    },
    {
      "description": "Filter to a single platform (for example \"youtube\"). Omit for every platform.",
      "in": "query",
      "name": "platform",
      "required": false,
      "schema": {
        "type": "string"
      }
    },
    {
      "description": "One of your profiles (prf_...). Defaults to your default profile when neither profileId nor accountId is sent.",
      "in": "query",
      "name": "profileId",
      "required": false,
      "schema": {
        "pattern": "^prf_[0-9A-Za-z]{22}$",
        "type": "string"
      }
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "x-aisa-status": "enabled",
  "x-aisa-pricing": {
    "description": "Included with your connected social accounts: AIsa does not charge per call.",
    "kind": "no_customer_charge"
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "scheme": "bearer",
        "type": "http"
      }
    }
  }
}
```

## 费用与请求规则

当前未提供请求参考价格；请使用接口支持的报价功能确认费用。 最终费用以账户适用价格及实际结算为准。

公开参考值不是你的账户报价或最终扣费。对于支持 X-AISA-Cost-Mode: quote 的接口，执行前请针对完整请求获取免费报价；执行后通过响应费用元数据和 Usage Logs 核对结算费用。1 USD 等于 1,000,000 个整数 micro-USD。


## OpenAPI

````yaml reference-contracts/zh/aisa-social.json GET /apis/v1/social/analytics/delta
openapi: 3.1.0
info:
  description: ''
  title: AIsa Social
  version: '1'
  x-aisa-capabilities:
    idempotency: Idempotency-Key
    max_price: X-AISA-Max-Price-USD
    quote:
      header: X-AISA-Cost-Mode
      value: quote
  x-aisa-configured-paths: []
  x-aisa-document:
    facts_hash: 99acb5bddbedc8d3352e7c4fef86c3e6d8852c14772bcd18dbcf415237a642bd
    generator_version: '3'
    protocol_version: '1'
    schema_version: '1'
    composer_version: '27'
    response_pending: []
    document_hash: sha256:7f9742cb1ea233c648dcb290fdfbf24e53a27dcf900ec1386f83a8ef2e6b1bf9
  x-aisa-plans:
    builder: 1
    display_plan: payg
    payg: 1
    similarweb_payg: 1
    team: 1
    version: e70a0959cd1b93b84ae16dffd7a538273ddb9c1d46277b82823c37ff1b6bc0fb
  x-aisa-provider: aisa-social
  x-aisa-catalogs:
    aisa-social:
      title: AIsa Social
      description: ''
      x-aisa-document:
        facts_hash: 99acb5bddbedc8d3352e7c4fef86c3e6d8852c14772bcd18dbcf415237a642bd
        generator_version: '3'
        protocol_version: '1'
        schema_version: '1'
      x-aisa-plans:
        builder: 1
        display_plan: payg
        payg: 1
        similarweb_payg: 1
        team: 1
        version: e70a0959cd1b93b84ae16dffd7a538273ddb9c1d46277b82823c37ff1b6bc0fb
      x-aisa-capabilities:
        idempotency: Idempotency-Key
        max_price: X-AISA-Max-Price-USD
        quote:
          header: X-AISA-Cost-Mode
          value: quote
servers:
  - url: https://api.aisa.one
security:
  - bearerAuth: []
paths:
  /apis/v1/social/analytics/delta:
    get:
      summary: Analytics changed since a cursor
      description: >-
        Analytics. Read only; needs social:read. Ids are yours: accounts
        acc_..., profiles prf_..., posts pst_...; every other object id is
        checked against your accounts before the call. Without profileId or
        accountId it reads your default profile.


        Cursor feed of the analytics snapshots that CHANGED, across every
        account you can read, in one paginated stream. Built for integrations
        that would otherwise call `GET /apis/v1/social/analytics` once per
        connected account.


        Each page carries changes from many accounts at once, so your call count
        scales with how much actually changed rather than with how many accounts
        you have.


        Measured against a fleet of roughly 1,600 connected accounts: about
        1,599 per-account analytics calls an hour became about 205 delta calls
        an hour, a 7.8x reduction.


        ### Bootstrap once, then stay in sync


        1. Take the cursor FIRST: call this endpoint with NO `cursor`. It
        answers with an empty `data` array plus the feed's current position in
        `nextCursor`.

        2. Load your baseline from `GET /apis/v1/social/analytics`, the
        historical endpoint. This one is a rolling 7-day change log and cannot
        replay history.

        3. Resume from the cursor you took before the baseline.


        Taking the cursor afterwards instead drops every change that lands while
        the baseline is loading: it is in neither the row you already read nor
        the feed you resume behind it.


        The overlap this order creates is safe. Metrics are absolute values
        rather than increments, so draining it leaves every post on its newest
        value.


        `nextCursor` is present on every response, empty pages included, so you
        always have something to advance with.


        ### Ordering


        Entries come back oldest first, in the order the feed received them.
        That order is NOT `syncedAt`. `syncedAt` is stamped when an account's
        sync cycle started, and a slow cycle writes its rows after a faster
        cycle that started later, so `syncedAt` can go backwards between
        consecutive entries.


        Do not sort, filter or resume on `syncedAt`. The cursor is the only
        stable position, and it is opaque: pass it back verbatim, and do not
        parse, construct or compare cursors.


        ### `hasMore: false` does not mean the feed ended


        This stream has no end and `nextCursor` is never null.


        - `hasMore: true`: more changes are already waiting, so call again
        straight away.

        - `hasMore: false`: you are caught up. Keep the cursor and poll again on
        your normal interval.


        ### The newest changes settle before they are served


        The feed deliberately holds back its last few seconds of writes, so that
        a row can never become visible behind a cursor you have already advanced
        past.


        A read issued the instant an `analytics.synced` webhook lands will
        therefore often return an empty page for that account. Do not read an
        empty page as "nothing changed": poll again with the SAME cursor you
        last used rather than advancing.


        ### Repeats inside one instant


        A sync cycle occasionally records the same post twice at the same feed
        position. When that happens the feed delivers one of those rows, not
        both.


        Measured over a day of production traffic, about 1.3% of rows fall in
        such a group and 99.4% of those groups are identical rows, so this is
        far more often deduplication than loss.


        Metrics are absolute values rather than increments, so a later entry for
        the same post supersedes an earlier one.


        ### Retention is 7 days


        Changes older than that leave the feed. A cursor older than 6 days is
        rejected with a `400` (a day of margin, because expiry is lazy).


        Recover the same way you bootstrapped:


        1. Take a fresh cursor from a call to this endpoint with no `cursor`.

        2. Re-load from `GET /apis/v1/social/analytics`.

        3. Resume from that cursor.


        A consumer that polls at least daily never reaches this.


        ### Webhook and access


        Pairs with the `analytics.synced` webhook, so changes can be read on
        notification instead of on a timer. That event carries no cursor of its
        own: keep using the `nextCursor` this endpoint gave you.


        Requires the same analytics access as `GET /apis/v1/social/analytics`,
        and shares the stricter per-second rate-limit window applied to
        analytics endpoints.


        <!-- aisa-pricing:start -->

        当前未提供请求参考价格；请使用接口支持的报价功能确认费用。 最终费用以账户适用价格及实际结算为准。

        <!-- aisa-pricing:end -->
      operationId: get_aisa_social_apis_v1_social_analytics_delta
      parameters:
        - description: >-
            Opaque cursor from a previous response's `nextCursor`. Omit it to
            start from

            now: the response is then an empty page carrying the feed's current
            position.

            Rejected with a `400` when malformed, or when older than the
            retention window.
          in: query
          name: cursor
          required: false
          schema:
            type: string
        - description: Page size. Out-of-range values are a 400, never a silent clamp.
          in: query
          name: limit
          required: false
          schema:
            default: 50
            maximum: 100
            minimum: 1
            type: integer
        - description: >-
            Filter to a single platform (for example "youtube"). Omit for every
            platform.
          in: query
          name: platform
          required: false
          schema:
            type: string
        - description: >-
            One of your profiles (prf_...). Defaults to your default profile
            when neither profileId nor accountId is sent.
          in: query
          name: profileId
          required: false
          schema:
            pattern: ^prf_[0-9A-Za-z]{22}$
            type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  data:
                    description: >-
                      Changed snapshots, oldest first, in the order the feed
                      received them. Empty on

                      the bootstrap call (no `cursor` supplied) and whenever
                      nothing has changed

                      since your cursor.
                    items:
                      description: >-
                        One changed analytics snapshot. Metrics are the absolute
                        values recorded at

                        `syncedAt`, not the amount they moved by since the
                        previous snapshot, so a later

                        entry for the same `postId` always supersedes an earlier
                        one.
                      properties:
                        accountId:
                          description: One of your social accounts (acc_...).
                          pattern: ^acc_[0-9A-Za-z]{22}$
                          type: string
                        isDeleted:
                          description: >-
                            True when the post was detected as deleted on the
                            platform at this sync
                          type: boolean
                        metrics:
                          description: >-
                            Metrics a platform does not report are 0, not
                            absent.
                          properties:
                            audienceCountries:
                              additionalProperties:
                                type: number
                              description: >-
                                TikTok business lane: viewer-country shares
                                keyed by ISO-3166 alpha-2, fractions 0 to 1, top
                                20 with the tail in `other`. Empty object
                                elsewhere.
                              type: object
                            audienceTypes:
                              additionalProperties:
                                type: number
                              description: >-
                                TikTok business lane: follower / nonFollower and
                                newViewer / returnViewer shares, fractions 0 to
                                1. Empty object elsewhere.
                              type: object
                            clicks:
                              type: integer
                            comments:
                              type: integer
                            completionRate:
                              description: >-
                                TikTok business lane: share of viewers who
                                watched to the end, 0 to 1
                              type: number
                            follows:
                              description: >-
                                Follows attributed to this post (Instagram feed
                                and stories, Facebook Reels, TikTok business
                                lane)
                              type: integer
                            igReelsAvgWatchTime:
                              description: >-
                                Average watch time per play, in milliseconds
                                (Instagram Reels, Facebook Reels, TikTok
                                business videos)
                              type: integer
                            igReelsVideoViewTotalTime:
                              description: >-
                                Total watch time including replays, in
                                milliseconds (Instagram Reels, Facebook Reels,
                                TikTok business videos)
                              type: integer
                            impressionSources:
                              additionalProperties:
                                type: number
                              description: >-
                                TikTok business lane: share of views by surface
                                (forYou, follow, search, personalProfile, sound,
                                directMessage, other), fractions 0 to 1. Empty
                                object elsewhere.
                              type: object
                            impressions:
                              type: integer
                            likes:
                              type: integer
                            profileViews:
                              description: >-
                                Instagram feed posts (images and carousels; 0 on
                                Reels and Stories) and TikTok business lane:
                                profile views attributed to the post
                              type: integer
                            reach:
                              type: integer
                            reelsSkipRate:
                              description: Instagram Reels skip rate, 0 to 1
                              type: number
                            replays:
                              description: >-
                                Facebook Reels only: plays that were replays. 0
                                elsewhere.
                              type: integer
                            reposts:
                              type: integer
                            retentionCurve:
                              additionalProperties:
                                type: number
                              description: >-
                                Share of viewers still watching at each second
                                of playback, as fractions 0 to 1. Filled for
                                Facebook Reels and the TikTok business lane; an
                                empty object for other media and platforms.


                                **Facebook Reels** (Meta
                                post_video_retention_graph):


                                - The base is a subset of plays that Meta
                                selects and does not expose. On small Reels it
                                can be far below views (6 of 51 measured), so a
                                value is a share of that subset, not of all
                                plays.

                                - Keys are whole seconds from the start of a
                                play ("3" is the share still watching at 3 s).

                                - Loops count as continued playback, so a short
                                Reel's curve runs past its length (an 8 s Reel
                                has keys "0" to "12").

                                - Meta returns at most 41 points, so a long Reel
                                covers only its first 40 s.


                                **TikTok** (accounts connected through the
                                TikTok for Business app, from TikTok
                                video_view_retention): share still watching at
                                each whole second, fractions 0 to 1, at most 61
                                points, thinned evenly on videos longer than 60
                                s.
                              type: object
                            saves:
                              type: integer
                            sends:
                              type: integer
                            shares:
                              type: integer
                            views:
                              type: integer
                            websiteClicks:
                              description: >-
                                TikTok business lane: website-link clicks
                                attributed to the post (also inside clicks)
                              type: integer
                          required:
                            - audienceCountries
                            - audienceTypes
                            - clicks
                            - comments
                            - completionRate
                            - follows
                            - igReelsAvgWatchTime
                            - igReelsVideoViewTotalTime
                            - impressionSources
                            - impressions
                            - likes
                            - profileViews
                            - reach
                            - reelsSkipRate
                            - reposts
                            - saves
                            - sends
                            - shares
                            - views
                            - websiteClicks
                          type: object
                        platform:
                          type: string
                        platformPostId:
                          description: >-
                            Platform-side post ID (for example the YouTube video
                            ID)
                          type: string
                        postId:
                          description: >-
                            External post ID. The same identifier as
                            `posts[]._id` in GET /apis/v1/social/analytics.
                          type: string
                        profileId:
                          description: One of your profiles (prf_...).
                          pattern: ^prf_[0-9A-Za-z]{22}$
                          type: string
                        publishedAt:
                          description: When the post was published, ISO-8601 UTC
                          format: date-time
                          type: string
                        syncedAt:
                          description: >-
                            When the sync cycle that produced this snapshot
                            STARTED, ISO-8601 UTC.


                            This is NOT the order entries arrive in and not a
                            resume point: a slow cycle writes its rows after a
                            faster cycle that started later, so `syncedAt` can
                            go backwards between consecutive entries. Use
                            `nextCursor` to resume.
                          format: date-time
                          type: string
                      required:
                        - accountId
                        - isDeleted
                        - metrics
                        - platform
                        - platformPostId
                        - postId
                        - profileId
                        - publishedAt
                        - syncedAt
                      type: object
                    type: array
                  hasMore:
                    description: >-
                      True when more changes are already waiting past
                      `nextCursor`, so call again

                      immediately. False means you are caught up: keep
                      `nextCursor` and poll again

                      later. This feed never ends, so `hasMore: false` does NOT
                      mean `nextCursor`

                      is null.
                    type: boolean
                  nextCursor:
                    description: >-
                      Cursor to send on the next call. ALWAYS present, including
                      on an empty page,

                      so you always have something to advance with, and it never
                      moves backwards.

                      Opaque: pass it back verbatim, and do not parse, construct
                      or compare cursors.
                    type: string
                required:
                  - data
                  - hasMore
                  - nextCursor
                type: object
          description: One page of changed analytics
        default:
          content:
            application/json:
              schema:
                properties:
                  error:
                    properties:
                      code:
                        type:
                          - string
                          - 'null'
                      details:
                        additionalProperties: {}
                        type:
                          - object
                          - 'null'
                      message:
                        type:
                          - string
                          - 'null'
                      param:
                        type:
                          - string
                          - 'null'
                      retryHint:
                        enum:
                          - never
                          - after_delay
                          - after_reconnect
                          - reconcile_first
                          - null
                        type:
                          - string
                          - 'null'
                      retryable:
                        type:
                          - boolean
                          - 'null'
                      type:
                        type:
                          - string
                          - 'null'
                    type:
                      - object
                      - 'null'
                type: object
          description: Error. The code field is stable; see the error catalog.
components:
  securitySchemes:
    bearerAuth:
      scheme: bearer
      type: http

````

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