Reporting API — reading analytics, reviews and publishing data
Everything you see in Postwoo's Stats, Love inbox and Posts pages is available over the REST API at https://YOUR-POSTWOO-DOMAIN/api/v1. The machine-readable reference is at /api/v1/openapi.json (also rendered at /docs); this guide covers the reporting use case end to end.
1. Authentication
Create an API credential in Settings → API credentials (or php bin/postwoo token:create --org=<slug> --name=Dashboards --scopes=analytics:read,reviews:read,posts:read,clients:read,accounts:read,integrations:read). The token is shown once and starts with pwo_.
export POSTWOO=https://postwoo.example.com/api/v1
export TOKEN=pwo_…
curl -H "Authorization: Bearer $TOKEN" "$POSTWOO/clients"
Scopes for reporting: clients:read, accounts:read, analytics:read, reviews:read, posts:read, integrations:read. A credential can be restricted to specific clients; requests for other clients return 403. Rate limit: 600 requests per minute per credential (X-RateLimit-Remaining, Retry-After on 429).
Responses wrap data in data; list endpoints add meta.pagination (page, per_page, total, total_pages) and links. Errors are { "error": { "code", "message", "details", "request_id" } }.
2. The one rule: unavailable is not zero
Platforms publish metrics late (Google Business Profile lags two to three days), hide some metrics for small audiences, or do not offer a metric at all. Postwoo stores what the platform said and nothing else. Every number comes with an availability flag:
availability | Meaning | How to render |
|---|---|---|
available | The platform returned a value | Show the value |
delayed | The platform has not published this day yet | Hatched gap, "pending" |
unavailable | The platform returned no data for the period | Gap, "n/a" |
not_supported | The platform has no such metric | Omit the metric for that platform |
Totals come with available_days, delayed_days, unavailable_days so you can say "28-day total over 25 reported days". Never fill a gap with 0 and never compare two periods with different coverage without saying so; change_pct is null whenever Postwoo would have to guess.
3. Finding clients and accounts
curl -H "Authorization: Bearer $TOKEN" "$POSTWOO/clients?per_page=100"
curl -H "Authorization: Bearer $TOKEN" "$POSTWOO/accounts?client_id=CLIENT_ID"
curl -H "Authorization: Bearer $TOKEN" "$POSTWOO/accounts/ACCOUNT_ID/locations" # Google locations
Clients carry external_ref and settings.external_ids so you can map them to records in your own system.
4. Normalised summary (cards for a dashboard)
curl -H "Authorization: Bearer $TOKEN" \
"$POSTWOO/analytics/summary?client_id=CLIENT_ID&from=2026-09-01&to=2026-09-30&compare=previous_period"
{
"data": {
"period": { "start": "2026-09-01", "end": "2026-09-30" },
"cards": [
{ "key": "impressions", "label": "Impressions", "value": 98412, "previous": 90211, "change_pct": 9.1,
"availability": "available", "platforms": ["google_business_profile", "facebook"],
"sources": [ { "platform": "google_business_profile", "platform_metric_key": "BUSINESS_IMPRESSIONS_DESKTOP_MAPS" }, … ] },
{ "key": "link_clicks", "label": "Link clicks", "value": 2410, "previous": null, "change_pct": null, "availability": "delayed", … }
],
"by_platform": [ { "platform": "google_business_profile", "label": "Google Business Profile", "cards": [ … ] } ],
"series": [ { "date": "2026-09-01", "impressions": 3310, "actions": 71 }, { "date": "2026-09-29", "impressions": null, "actions": null } ]
}
}
sources tells you which platform metrics were combined into each card, so a client can always trace a number back to the platform's own definition.
5. Platform-true metrics (per account, per metric, per day)
curl -H "Authorization: Bearer $TOKEN" \
"$POSTWOO/analytics/accounts?client_id=CLIENT_ID&from=2026-09-01&to=2026-09-30&metrics=BUSINESS_IMPRESSIONS_DESKTOP_MAPS,CALL_CLICKS"
Each entry has account, optional location, and metrics[] with platform_metric_key, normalized_metric_key, label, unit, definition (the platform's wording), total {value, available_days, unavailable_days, delayed_days} and series[] {date, value, availability}. Use GET /analytics/metrics?platform=… for the full catalogue with definitions and caveats.
Add &format=csv to download the same data as CSV (one row per account × metric × day with an availability column; empty cell when there is no value).
6. Post performance
curl -H "Authorization: Bearer $TOKEN" "$POSTWOO/analytics/posts?client_id=CLIENT_ID&from=2026-09-01&to=2026-09-30&platform=facebook"
Returns one row per published variant: post_id, platform, account, published_at, external_url, metrics[] {platform_metric_key, label, value, availability, collected_at}. Also available with format=csv.
7. Reviews
curl -H "Authorization: Bearer $TOKEN" "$POSTWOO/reviews/stats?client_id=CLIENT_ID"
curl -H "Authorization: Bearer $TOKEN" "$POSTWOO/reviews?client_id=CLIENT_ID&from=2026-09-01&per_page=50&sort=newest"
stats gives average_rating, total, by_rating, unreplied, response_rate, median_response_hours, by_location[]. Each review carries rating, reviewer, body, created_at, location, reply {status, body, published_at} and sentiment. Replies are only ever written by Postwoo users through the app or POST /reviews/{id}/reply; the API never invents a reply.
8. Publishing data
GET /posts?client_id&status=published&from&to— list withvariants[]per platform (external_url,published_at,last_error).GET /posts/{id}— full detail incl. attempts.GET /calendar?from&to&client_id— what is scheduled.GET /jobs/stats— queue health, if you monitor Postwoo itself.
9. Freshness and provenance
GET /analytics/snapshots?account_id=… lists every sync run: period, status, metrics_collected, metrics_unavailable, data_source and errors. Put the latest finished_at on your dashboard as "data as of". The demo workspace's data source is labelled demo-seed (not a platform source) so it can never be mistaken for real numbers.
10. Webhooks instead of polling
curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" "$POSTWOO/webhooks" \
-d '{"url":"https://dashboards.example.com/hooks/postwoo","events":["post.published","post.failed","review.received","analytics.snapshot.completed"]}'
The secret is returned once. Verify X-Postwoo-Signature: sha256=HMAC_SHA256(secret, timestamp + "." + rawBody) with X-Postwoo-Timestamp within five minutes. Deliveries retry five times. analytics.snapshot.completed is the right trigger to refresh a client dashboard.
11. Client-ready reports
Postwoo can also produce finished, branded monthly reports that follow the same availability rules (share links under /reports/links once that feature is enabled on your instance; see the API reference for the current endpoints).
12. Code samples
PHP
$ch = curl_init("$postwoo/analytics/summary?client_id=$clientId&from=2026-09-01&to=2026-09-30&compare=previous_period");
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer $token"]]);
$summary = json_decode(curl_exec($ch), true)['data'];
foreach ($summary['cards'] as $card) {
echo $card['label'], ': ', $card['value'] ?? 'n/a (' . $card['availability'] . ')', PHP_EOL;
}
JavaScript
const res = await fetch(`${POSTWOO}/analytics/accounts?client_id=${clientId}&from=2026-09-01&to=2026-09-30`, { headers: { Authorization: `Bearer ${token}` } });
const { data } = await res.json();
for (const acc of data.accounts) for (const m of acc.metrics) {
const points = m.series.map((p) => (p.availability === 'available' ? p.value : null)); // null = gap in the chart
}
Python
import requests
r = requests.get(f"{POSTWOO}/reviews/stats", params={"client_id": client_id}, headers={"Authorization": f"Bearer {token}"})
stats = r.json()["data"]
print(stats["average_rating"], stats["response_rate"])
13. Errors you will meet
| Status | code | Meaning |
|---|---|---|
| 401 | unauthenticated | Missing or revoked token |
| 403 | forbidden | Scope or client restriction |
| 404 | not_found | Wrong id or another organization's record |
| 422 | validation_failed | Bad query (details.fields) |
| 429 | rate_limited | Back off for Retry-After seconds |
Log the request_id when you report an issue.