postlake-dev Analytics API

Per-post and cross-platform metrics

Operations 5

GET /v1/posts/{id}/analytics Get post analytics #
POST /v1/analytics/refresh Request background analytics refresh #
GET /v1/analytics/refresh Get analytics refresh progress #
GET /v1/analytics Get cross-platform analytics #
GET /v1/analytics/methodology Read analytics methodology #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/postlake-dev:postlake-dev-analytics-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

postlake-dev-analytics-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: PostLake Analytics API
  version: 1.0.0
  description: Unified social media API. One API key, 10 platforms, normalised responses. Built for agents and developers.
  contact:
    url: https://postlake.dev
servers:
- url: https://api.postlake.dev
security:
- bearerAuth: []
tags:
- name: Analytics
  description: Per-post and cross-platform metrics
paths:
  /v1/posts/{id}/analytics:
    get:
      tags:
      - Analytics
      summary: Get post analytics
      description: Live per-post analytics by destination. measuredAt says when the read completed. targets[].reports identifies metrics the network exposes; targets[].metrics is null when insights are unavailable, not zero.
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  postId:
                    type: string
                  createdAt:
                    type: string
                    format: date-time
                  measuredAt:
                    type: string
                    format: date-time
                    description: When PostLake completed this live metrics read.
                  totals:
                    $ref: '#/components/schemas/Metrics'
                  targets:
                    type: array
                    items:
                      type: object
                      properties:
                        account:
                          type: string
                        platform:
                          $ref: '#/components/schemas/Platform'
                        handle:
                          oneOf:
                          - type: string
                          - type: 'null'
                        remoteId:
                          oneOf:
                          - type: string
                          - type: 'null'
                        url:
                          oneOf:
                          - type: string
                          - type: 'null'
                        reports:
                          type: array
                          items:
                            type: string
                          description: Metric names this network reports. An unreported metric must not be displayed as zero.
                        metrics:
                          oneOf:
                          - $ref: '#/components/schemas/Metrics'
                          - type: 'null'
                      required:
                      - account
                      - platform
                      - handle
                      - remoteId
                      - url
                      - reports
                      - metrics
                required:
                - postId
                - createdAt
                - measuredAt
                - totals
                - targets
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: Post not found
      operationId: getV1PostsByIdAnalytics
      x-operation-id-source: derived
  /v1/analytics/refresh:
    post:
      tags:
      - Analytics
      summary: Request background analytics refresh
      description: 'Queue a workspace analytics refresh and return immediately. Requests are deduplicated while a job is active, with a five-minute start cooldown. Optional ifStale=true reuses a collection updated within fifteen minutes; the dashboard uses it once on entry without restarting collection on filter changes. Recent eligible content is checked first through bounded continuations on a separate queue. During reliability incidents, collection can be paused: new requests return 503, analytics_refresh_unavailable and Retry-After: 60. Stored reports remain available. The same explicit refresh operation is available through get_analytics with refresh=true in MCP.'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                ifStale:
                  type: boolean
                  default: false
                  description: Reuse a recently updated collection instead of starting another job. Automatic-entry freshness interval is fifteen minutes; provider eligibility and cooldowns still apply.
      responses:
        '202':
          description: Queued, already active, or within the start cooldown
          content:
            application/json:
              schema:
                type: object
                properties:
                  jobId:
                    type: string
                  state:
                    type: string
                    enum:
                    - queued
                    - running
                    - complete
                    - failed
                  startedAt:
                    type: string
                    format: date-time
                  updatedAt:
                    type: string
                    format: date-time
                  posts:
                    type: integer
                  errors:
                    type: integer
                  cursor:
                    oneOf:
                    - type: string
                    - type: 'null'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '503':
          description: 'Collection temporarily paused or unavailable. Retry-After: 60. Read stored reports without starting another collection job.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      operationId: postV1AnalyticsRefresh
      x-operation-id-source: derived
    get:
      tags:
      - Analytics
      summary: Get analytics refresh progress
      description: Read the most recent workspace refresh state. Null means no refresh has been requested. collectionPaused is true while collection is suspended for service reliability; an existing queued or running job is not necessarily advancing. Complete means eligible collection finished, not that every provider returned every metric. errors counts unavailable readings; prior valid readings are preserved. MCP get_analytics includes this status in its response. Completion invalidates private saved reports immediately; measuredAt remains the metric collection timestamp.
      responses:
        '200':
          description: Progress or null
          content:
            application/json:
              schema:
                oneOf:
                - type: object
                  properties:
                    jobId:
                      type: string
                    state:
                      type: string
                      enum:
                      - queued
                      - running
                      - complete
                      - failed
                    collectionPaused:
                      type: boolean
                      description: True when collection is suspended; queued/running jobs may not advance.
                    startedAt:
                      type: string
                      format: date-time
                    updatedAt:
                      type: string
                      format: date-time
                    posts:
                      type: integer
                    errors:
                      type: integer
                    cursor:
                      oneOf:
                      - type: string
                      - type: 'null'
                - type: 'null'
        '401':
          $ref: '#/components/responses/Unauthorized'
      operationId: getV1AnalyticsRefresh
      x-operation-id-source: derived
  /v1/analytics:
    get:
      tags:
      - Analytics
      summary: Get cross-platform analytics
      description: 'Read private saved reports for all collected destinations, without a 50-post sample or live provider calls. Cold or invalidated reports use a globally limited read-only builder; busy admission returns 429 with Retry-After: 60 and database failures return 503. Same-filter saved results can be retained with snapshot.stale=true. measuredAt is collection time and snapshot.generatedAt is preparation time. Production reads check the durable generation directly, without an additional edge-cache delay after collection completes. Period mode reports observed counter growth, including older content; Content mode reports lifetime performance of content published in the range. Totals, current, chart and breakdown share mode and filters. Rankings contain up to twenty destinations. POST /v1/analytics/refresh queues collection; GET that route reports progress. GET /v1/analytics/methodology defines reconciliation and freshness.'
      parameters:
      - name: period
        in: query
        schema:
          type: string
          default: 30d
          pattern: ^[0-9]+d$
      - name: profile
        in: query
        schema:
          type: string
        description: Profile username. Omit for the whole workspace.
      - name: mode
        in: query
        schema:
          type: string
          enum:
          - period
          - content
          default: period
        description: Observed period activity, or lifetime performance of content published in the range.
      - name: sort
        in: query
        schema:
          type: string
          enum:
          - views
          - likes
          - comments
          - engagement
          - engagementRate
          - impressions
          - clicks
          default: engagement
        description: Rank the complete matching destination population before limiting to 20. Unavailable readings sort last. Rates require a positive reported audience count.
      - name: channel
        in: query
        schema:
          type: string
        description: Connected social account ID. Shared posts contribute only matching destinations.
      - name: network
        in: query
        schema:
          type: string
        description: Optional platform name.
      - name: from
        in: query
        schema:
          type: string
          format: date
        description: Inclusive first UTC date, YYYY-MM-DD.
      - name: to
        in: query
        schema:
          type: string
          format: date
        description: Inclusive last UTC date, YYYY-MM-DD. Maximum range 365 days.
      - name: agent
        in: query
        schema:
          type: string
        description: Stable creator client ID, not display name. Unknown historical attribution is not inferred.
      - name: origin
        in: query
        schema:
          type: string
          enum:
          - dashboard
          - api
          - agent
        description: Recorded interface, separate from creator identity. Account-wide supplements are omitted for actor/interface filters.
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  period:
                    type: string
                  since:
                    type: string
                    format: date-time
                  mode:
                    type: string
                    enum:
                    - period
                    - content
                  until:
                    type: string
                    format: date
                  measuredAt:
                    type: string
                    format: date-time
                    nullable: true
                  snapshot:
                    type: object
                    description: Private saved report metadata. generatedAt is report preparation, not metric collection. stale means rebuilding failed or a newer collection invalidated this report.
                    properties:
                      generatedAt:
                        type: string
                        format: date-time
                      stale:
                        type: boolean
                    required:
                    - generatedAt
                    - stale
                  rankingScope:
                    type: string
                    const: destinations
                  rankingMetric:
                    type: string
                    enum:
                    - views
                    - likes
                    - comments
                    - engagement
                    - engagementRate
                    - impressions
                    - clicks
                  averageViews:
                    type: object
                    nullable: true
                    description: 'Content mode only: lifetime views divided by destinations explicitly reporting views. Includes measured zeros, excludes unknown readings. A shared post counts once per destination.'
                    properties:
                      value:
                        type: number
                      destinations:
                        type: integer
                    required:
                    - value
                    - destinations
                  nativeExtrasBasis:
                    type: string
                    const: latest_lifetime
                    description: Supplemental native extras are lifetime counters, never period activity.
                  posts:
                    type: integer
                    description: Distinct posts with collected readings published in the range, not every published post.
                  totals:
                    $ref: '#/components/schemas/Metrics'
                  methodologyUrl:
                    type: string
                    format: uri
                    description: Machine-readable metric and reconciliation definitions.
                  current:
                    type: object
                    nullable: true
                    description: 'Selected-mode totals for the requested window. Period: observed activity. Content: lifetime results of published content.'
                  previous:
                    type: object
                    nullable: true
                    description: Same mode and filters in the preceding equal-length window. Null means no usable observations.
                  timeseries:
                    type: array
                    description: UTC daily observed growth in Period mode, or lifetime results by publication date in Content mode. Includes likes, comments and engagementRate. A daily rate is null without usable audience counts, not zero.
                    items:
                      type: object
                  byChannel:
                    type: array
                    description: One row per social account with readings. Optional connected distinguishes live connections from retained disconnected history; disconnected accounts can have available post metrics. Same verified identity/destination in the same profile reuses its channel ID on reconnection. Native profile insights in account are separate from post totals. Optional accountPeriod gives days (30) and capturedAt for the independent rolling profile window; followers is current or null. Threads account.views means profile views. Profile-only rows have posts=0, available=false and empty reports for post metrics. Native readings are omitted with custom dates, agent or origin filters. fieldCoverage describes returned post counters, not historical daily coverage.
                    items:
                      type: object
                  topPosts:
                    type: array
                    description: Up to 20 ranked destinations across the complete matching population, with optional thumbnailUrl. A shared post can appear more than once.
                    items:
                      type: object
                  derived:
                    type: object
                    description: Engagement, blended engagement rate and CTR. See methodology for denominators.
                  byPlatform:
                    type: array
                    items:
                      type: object
                      properties:
                        platform:
                          $ref: '#/components/schemas/Platform'
                        posts:
                          type: integer
                        available:
                          type: boolean
                        metrics:
                          $ref: '#/components/schemas/Metrics'
                        reports:
                          type: array
                          items:
                            type: string
                          description: Metrics that this adapter can report; absence is not a measured zero.
                        engagementRate:
                          type: number
                          nullable: true
                      required:
                      - platform
                      - posts
                      - available
                      - metrics
                required:
                - period
                - since
                - posts
                - totals
                - byPlatform
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          description: 'Saved report builder is busy. Retry-After: 60. Retain the previous report and wait before retrying.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '503':
          description: 'No saved report is available and preparation is temporarily unavailable. Retry-After: 60.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      operationId: getV1Analytics
      x-operation-id-source: derived
  /v1/analytics/methodology:
    get:
      tags:
      - Analytics
      summary: Read analytics methodology
      description: Machine-readable definitions for normalised metrics, coverage, freshness, availability, comparisons, and per-network reporting. Read this before comparing metrics across networks or explaining a zero.
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  version:
                    type: integer
                  scope:
                    type: string
                  freshness:
                    type: string
                  totals:
                    type: string
                  engagement:
                    type: string
                  engagementRate:
                    type: string
                  ctr:
                    type: string
                  comparisons:
                    type: string
                  timeseries:
                    type: string
                  availability:
                    type: string
                  profileFilter:
                    type: string
                  accountInsights:
                    type: string
                  reportsByPlatform:
                    type: object
                    additionalProperties:
                      type: array
                      items:
                        type: string
                required:
                - version
                - scope
                - freshness
                - totals
                - engagement
                - engagementRate
                - ctr
                - comparisons
                - timeseries
                - availability
                - profileFilter
                - accountInsights
                - reportsByPlatform
        '401':
          $ref: '#/components/responses/Unauthorized'
      operationId: getV1AnalyticsMethodology
      x-operation-id-source: derived
components:
  schemas:
    ErrorResponse:
      type: object
      properties:
        error:
          $ref: '#/components/schemas/NormalisedError'
      required:
      - error
    NormalisedError:
      type: object
      description: '`type` is the coarse category you route on and is deliberately small, so it cannot say WHICH input was wrong. `code`, `fix`, `docs` and `param` are what turn a rejection into something a caller can act on rather than guess at. They are optional only in the sense that not every error has all four.'
      properties:
        type:
          type: string
          description: Coarse category to route on, e.g. rate_limited, auth_expired, platform_rejected.
        message:
          type: string
        platform:
          oneOf:
          - $ref: '#/components/schemas/Platform'
          - type: 'null'
        retryable:
          type: boolean
        code:
          type: string
          description: Stable, granular handle to branch on, e.g. text_too_long. Also the anchor in the error reference.
        fix:
          type: string
          description: The next action, in plain words. Safe to show a person as-is.
        docs:
          type: string
          format: uri
          description: Where the rule is written down, so it is learned once rather than rediscovered.
        param:
          type: string
          description: The request field at fault, so a form can mark the right input.
      required:
      - type
      - message
      - retryable
    Metrics:
      type: object
      description: Numeric counters retain compatibility. Optional reported lists fields actually returned by this observation, including measured zero. Missing reported means legacy availability is unknown. Do not interpret an omitted field as measured zero.
      properties:
        reported:
          type: array
          items:
            type: string
            enum:
            - views
            - impressions
            - reach
            - likes
            - comments
            - shares
            - saves
            - clicks
          description: Counters actually returned in this reading. An empty array means none. Omitted for legacy evidence with unknown field coverage.
        impressions:
          type: integer
          description: Times the content was DISPLAYED.
        views:
          type: integer
          description: Times it was actually WATCHED or opened. A subset of impressions, not a synonym, and networks that report only one of the two leave the other at 0 rather than filing a watch as a display.
        reach:
          type: integer
        likes:
          type: integer
        comments:
          type: integer
        shares:
          type: integer
        saves:
          type: integer
        clicks:
          type: integer
        followers:
          type: integer
        extras:
          type: object
          additionalProperties:
            type: number
          description: 'Metrics this network reports that have no cross-platform equivalent, keyed by the network''s OWN metric name. Pinterest returns a video retention curve (VIDEO_START, VIDEO_10S_VIEW, QUARTILE_95_PERCENT_VIEW) plus PROFILE_VISIT and USER_FOLLOW; Facebook returns its own video and Page numbers. Keys are native and are never normalised, so it is always clear which network a figure came from and what it means there. Rates and averages are deliberately excluded: they cannot be summed across posts, and every one of them is derivable from the counts. Absent when the network reports nothing extra.'
      required:
      - impressions
      - reach
      - likes
      - comments
      - shares
      - saves
      - clicks
      - followers
    Platform:
      type: string
      enum:
      - bluesky
      - threads
      - x
      - linkedin
      - instagram
      - tiktok
      - facebook
      - youtube
      - pinterest
  responses:
    Unauthorized:
      description: Unauthorized
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              type: unauthorized
              message: missing or invalid api key
              retryable: false