Help

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:

availabilityMeaningHow to render
availableThe platform returned a valueShow the value
delayedThe platform has not published this day yetHatched gap, "pending"
unavailableThe platform returned no data for the periodGap, "n/a"
not_supportedThe platform has no such metricOmit 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 with variants[] 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

StatuscodeMeaning
401unauthenticatedMissing or revoked token
403forbiddenScope or client restriction
404not_foundWrong id or another organization's record
422validation_failedBad query (details.fields)
429rate_limitedBack off for Retry-After seconds

Log the request_id when you report an issue.

Ready to woo your clients?

Start free, connect a Google Business Profile, and see real reviews and metrics in the feed within minutes.