Docs · Public API

A small, honest REST API.

The Starvo Public API is a thin, read-only REST surface for pulling your reviews and stats into a website, BI tool, or internal dashboard. Two endpoints, keyed access, predictable response shape, no surprises.

Overview

  • Base URL: https://starvo.app
  • Plan requirement: Pro Max. API keys cannot be created on lower plans.
  • Endpoints: GET /api/public/reviews and GET /api/public/stats
  • Auth: the X-API-Key header, using a key of the form sk_live_... (an Authorization: Bearer header is also accepted)
  • Rate limit: 1,000 requests per hour per API key
  • Response shape: always { success, data, error }
  • Versioning: the URL path is the version contract. Breaking changes will introduce /api/public/v2/...

Getting an API key

  1. 1. Make sure your business is on the Pro Max plan (Dashboard → Billing).
  2. 2. Open your dashboard and find the “Advanced” section, which holds API Keys.
  3. 3. Click Create key. Name it (e.g. “production website”).
  4. 4. Copy the key immediately. It is shown once. Starvo stores only a SHA-256 hash; we cannot recover a lost key.
  5. 5. If you lose a key, delete it and create a new one. Treat keys like passwords.
Key prefix: all Starvo keys start with sk_live_. If a key you have starts with anything else, it is invalid (or test-mode, which is not exposed publicly yet).

Authentication

Pass the key in the X-API-Key header (this is the canonical form):

X-API-Key: sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

An Authorization: Bearer sk_live_... header is also accepted as a legacy fallback, so existing integrations keep working — but X-API-Key is the documented, recommended header.

The key is scoped to a single Starvo account and authorizes every business that account owns. All endpoints automatically filter results to the account that owns the key.

Rate limits

  • 1,000 requests per hour, per API key. Rolling window.
  • Over-quota requests return HTTP 429 with error: "Rate limit exceeded. Max 1000 requests per hour per API key."
  • If you have a use case that legitimately needs more, email support@starvo.app.

Response envelope

Every endpoint returns the same envelope:

{ "success": true, "data": <object | array | null>, "error": <string | null> }

On success, success is true, data contains the payload, and error is null. On failure, the reverse — success is false, data is null, and error contains a human-readable message.

Errors

HTTPError stringWhen
401Missing or invalid API key. Send your key as the X-API-Key header…No X-API-Key (or Bearer) header, or wrong key prefix.
401Invalid API keyKey not found or revoked.
403UPGRADE_REQUIRED: Public API access requires the Pro Max plan.Business is below Pro Max.
429Rate limit exceeded. Max 1000 requests per hour per API key.Over 1,000 requests in the last hour.
500Failed to fetch reviews / etc.Database error — rare; retry once.

GET /api/public/reviews

Returns published reviews for your business (private feedback is excluded). Most recent first. Paginated.

Query parameters

ParamTypeDefaultNotes
limitint501–100. Clamped if outside that range.
pageint11-indexed. Use page + limit for pagination.

Response shape

{ "success": true, "data": [ { "rating": 5, "comment": "Great food, great service.", "created_at": "2026-04-12T18:21:09.000Z" }, { "rating": 4, "comment": null, "created_at": "2026-04-11T20:04:00.000Z" } ], "error": null }

Comments are sanitized server-side (HTML stripped, truncated at 1,000 characters). Customer email addresses are never exposed — only the rating, the comment, and the timestamp.

GET /api/public/stats

Returns aggregated review statistics for your business.

Response shape

{ "success": true, "data": { "total_reviews": 1247, "average_rating": 4.6, "unresolved_count": 12, "rating_breakdown": { "1": 28, "2": 41, "3": 73, "4": 318, "5": 787 } }, "error": null }

average_rating is rounded to one decimal place. unresolved_count is the number of public reviews not yet marked resolved. rating_breakdown is keyed by star value (1–5). Private and deleted reviews are excluded. Same business-scoping rules as the reviews endpoint.

Examples

curl

# Fetch the 10 most recent reviews curl https://starvo.app/api/public/reviews?limit=10 \ -H "X-API-Key: sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" # Fetch stats curl https://starvo.app/api/public/stats \ -H "X-API-Key: sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Node.js (fetch)

const KEY = process.env.STARVO_API_KEY; async function fetchReviews(limit = 50, page = 1) { const res = await fetch( `https://starvo.app/api/public/reviews?limit=${limit}&page=${page}`, { headers: { 'X-API-Key': KEY } } ); const body = await res.json(); if (!body.success) throw new Error(body.error); return body.data; } async function fetchStats() { const res = await fetch( 'https://starvo.app/api/public/stats', { headers: { 'X-API-Key': KEY } } ); const body = await res.json(); if (!body.success) throw new Error(body.error); return body.data; }

Python (requests)

import os, requests KEY = os.environ["STARVO_API_KEY"] HEAD = {"X-API-Key": KEY} def fetch_reviews(limit=50, page=1): r = requests.get( "https://starvo.app/api/public/reviews", params={"limit": limit, "page": page}, headers=HEAD, timeout=10, ) body = r.json() if not body["success"]: raise RuntimeError(body["error"]) return body["data"] def fetch_stats(): r = requests.get( "https://starvo.app/api/public/stats", headers=HEAD, timeout=10, ) body = r.json() if not body["success"]: raise RuntimeError(body["error"]) return body["data"]

Changelog

  • v1 (current) — first public release. /api/public/reviews and /api/public/stats.
  • Coming: per-location filtering, sentiment + topic in the reviews payload, webhooks for new-review events.

Want a specific addition? Email support@starvo.app. API changes follow the rule: never break an existing endpoint shape. New fields are added, old fields are not removed. If we have to remove a field, it gets a new versioned URL.