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: 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