Read engagement, membership and content-safety analytics for one organization over a period.
This is a POST because the query is structured — an array of metric objects, each with
its own properties — but it is a safe, idempotent read. Retries change nothing.
Analytics are computed asynchronously. A metric whose figures are not in the cache
answers 202 Accepted with Retry-After and names itself under meta.pending.
Re-issue the identical request after that many seconds, and keep doing so while the
response is a 202 or meta.pending is non-empty — a single query may run for
minutes, so one retry is not always enough. A 202 is not a failure, and a pending
metric must never be read as zero.
The endpoint is opt-in while in pilot: it answers only for organizations Padlet has
enrolled, and enrollment is granted on the organization's owner. Ask Padlet support to
enroll an organization.
Requires an API key belonging to an admin of the organization (a teacher, for a classroom).
Organization not enrolled, unknown id, or no access — every denial answers the same 404.
Opt-in while the endpoint is in pilotThis endpoint only answers for organizations Padlet has enrolled. Enrollment is granted on the organization's owner. Being an admin is not enough. Email [email protected] to ask for enrollment. Until then every call answers
404, the same response an unknown organization gets.
Findingorg_idYou do not compute it: read it from
GET /me?include=organizations, which lists everywksp_organization your key belongs to. This endpoint does not yet supportorg_organizations on padlet.org.
API keyYou will need to include your API key found here.
Please include the key in your header.
{"X-API-KEY": <api_key>}The key must belong to an admin of the organization, or a teacher for a classroom.
A 202 means poll, not failAnalytics are computed off the request path. A metric whose figures are not cached comes back as
202 Acceptedwith aRetry-Afterheader and its name undermeta.pending. Re-issue the identical request after that many seconds, and keep going while the response is a 202 ormeta.pendingis non-empty — a single query can run for minutes, so one retry is not always enough.Never read a pending metric as zero — it is absent from
data, not empty.
Reads engagement, membership and content-safety analytics for one organization over a period. The query is a structured array of metric objects, so this endpoint uses POST. It is still a safe, idempotent read, so a retry changes nothing. A retry does spend one of the hour's 60 requests, so wait for Retry-After rather than polling in a tight loop.
Request body
| Field | Type | Notes |
|---|---|---|
metrics | array | Required. 1 to 25 entries. A request may resolve to at most 50 queries in total, so a wide breakdown across many metrics has to be split across calls |
metrics[].name | string | A metric from the catalog below |
metrics[].<property> | string or array | See the catalog. An array breaks the metric down by that property — except userType, where an array is one role slice |
dimensions | array | ["month"] for a monthly series. Every metric in the call must have a monthly form |
from, to | string | YYYY-MM, inclusive. The range counts both endpoints and may span at most 24 of them, so to can be no more than 23 months after from. Send both or neither |
Omitting from and to reads the organization's current billing term, or all time if it has none.
from cannot precede January of five years ago, and to cannot be later than the current month. A range inside the 24-month cap but outside that window is still a 400.
Metric catalog
| Metric | Properties | Monthly |
|---|---|---|
memberCount | status: active, inactive, invited, joined, all · userType: roles | yes |
newMemberCount | — | yes |
padletCount | userType: roles, monthly only | yes |
postCount | userType: roles, monthly only | yes |
commentCount | userType: roles, monthly only | yes |
reactionCount | userType: roles, monthly only | yes |
visitorCount | — | yes |
peerInteractionCount | userType: roles — required | yes |
wallEngagementMinutes | userType: roles, plus anonymous | no |
attachmentCountByCategory | userType: roles — required | no |
scannedItemCount | — | no |
scannedItemCountByType | — | no |
avgScanTimeMs | — | no |
flaggedItemCount | — | no |
flaggedItemCountByCategory | — | no |
moderationCatchRatePercent | — | no |
topPadlets | sortBy: posts, visitors — required | no |
topUsers | sortBy: posts, padlets — required · userType: roles | no |
topMedia | mediaType: video, audio — required | no |
subscription | — | no |
userType takes organization roles, and which roles an organization has depends on its type:
| Organization | Roles |
|---|---|
| School | owner, admin, teacher, student, guest |
| Classroom | owner, teacher, student |
| Team | owner, admin, maker, contributor, guest |
The endpoint accepts any of these role names for any organization. It does not reject roles that organization type cannot have. Asking a classroom for admin returns a truthful zero, not an error. Send the roles the organization actually uses.
all must be sent alone. Mixed with role names — ["all", "teacher"] — it is read as a role and the request is a 400.
wallEngagementMinutes without userType, or with userType: all, covers the whole workspace. That includes anonymous visitors, not just members. Send an explicit role list to exclude them.
The six content-safety metrics run from scannedItemCount through moderationCatchRatePercent. They need content safety, and only schools and classrooms have it. Asking for them on a team organization returns a 400, not a misleading zero.
memberCount with status invited or joined has no all-members form, so those two require userType.
"Monthly only" means a metric's role-sliced form exists only as a monthly series. If you send userType for one of these metrics, you must also send dimensions: ["month"], or the request is a 400. Sending neither is fine: {"name": "postCount"} alone is a valid request and returns a single number. "Required" means the metric has no all-members form at all.
Responses carry the resolved properties, not a copy of the request. An omitted property comes back as all. A scalar userType comes back as a one-element array. Duplicates are collapsed. Match a meta.pending or meta.errors entry to your request using the name plus those resolved properties.
Membership counts describe the organization's current roster, not a historical snapshot. A member who has since left is excluded, even from a range that predates their departure. status: all and joined count current members who joined on or before to. status: active counts current members who contributed anything between from and to. status: inactive is the current roster size minus that active count. status: invited ignores from and to altogether. It counts the invitations outstanding right now. Everything else (posts, padlets, comments, reactions, visitors, engagement minutes) is scoped to the range.
What data holds
data holdsThe shape follows the request:
| Request | data |
|---|---|
| A plain count | a number — 42 |
dimensions: ["month"] | an array of monthly rows. The figure's key follows the metric: value for memberCount, padletCount, postCount, commentCount and peerInteractionCount, count for newMemberCount and reactionCount, visitors for visitorCount |
| A property given as an array | an object keyed by property value — { "active": 12, "inactive": 3 }. Each value keeps the shape that slice would have alone, so a monthly request gives an object of monthly arrays, and topPadlets with two sortBy values an object of ranked lists |
topPadlets, topUsers, topMedia | an array of at most ten objects, each with a hashid, a name or title, and its ranking figures. The cap is fixed — no limit parameter, no pagination |
attachmentCountByCategory | an array of { category, count } — a breakdown the metric always returns, with no property to ask for it |
scannedItemCountByType, flaggedItemCountByCategory | a numeric map keyed by scanned-item type or flagged category, likewise intrinsic |
subscription | an object with the billing dates, status and seat count |
subscription is the one metric whose fields are not a figure for the range. The dates and status describe the organization's current billing state. The seat count is as of the last day of the range, or today when the range ends in the future, which the default range usually does. It comes back null when the range spans more than one billing term, or when billing history doesn't reach that far back.
Errors
| Code | Meaning |
|---|---|
400 | Unknown metric, an invalid property value, a malformed or too-wide range, a duplicate entry, or more than 50 resolved queries |
401 | Missing or revoked API key |
404 | The organization does not exist, is not enrolled, or your key has no analytics access to it — the same response for all three |
406 | The Accept header named something else. Send application/vnd.api+json, or omit it |
415 | The Content-Type header named something else. Some clients default to form encoding; send application/json. A malformed body under an accepted type is a 400 with BAD_JSON instead |
429 | More than 60 requests in an hour for this API key and organization — a poll after a 202 spends one. Retry-After gives the wait in seconds |
Example
curl -X POST https://api.padlet.dev/v1/organizations/$ORG_ID/analytics \
-H "X-API-KEY: $PADLET_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"metrics": [
{ "name": "padletCount" },
{ "name": "memberCount", "status": "active", "userType": ["teacher", "admin", "owner"] }
],
"dimensions": ["month"]
}'{
"data": {
"id": "wksp_1oRLGeKoGK9DPvOw",
"type": "organizationAnalytics",
"attributes": {
"metrics": [
{ "name": "padletCount", "properties": { "userType": "all" }, "data": [{ "month": "2026-08", "value": 1 }] },
{
"name": "memberCount",
"properties": { "status": "active", "userType": ["teacher", "admin", "owner"] },
"data": [{ "month": "2026-08", "value": 1 }]
}
]
}
},
"meta": { "status": "ready", "dateRange": { "from": "2026-08-03", "to": "2027-08-03" } }
}Not every metric has a monthly form for every slice: memberCount has one for active with a role slice, so asking for ["active", "inactive"] alongside dimensions: ["month"] is a 400 naming the combination.
