REST API Reference
Use the SayWall REST API to fetch your approved testimonials and display them in custom UIs, mobile apps, or any platform.
Updated March 27, 2026
Overview
The SayWall REST API lets you fetch your approved testimonials programmatically. Build custom testimonial displays, integrate with mobile apps, or pipe data into your own systems.
Requirements:
- Pro plan (API access is a Pro feature)
- API key (create one in Dashboard > Developer > API)
Base URL:
https://saywall.io/api/v1
Authentication
All API requests require a Bearer token in the Authorization header.
Creating an API Key
- Go to Dashboard > Developer > API
- Click Create and give your key a name
- Copy the key immediately — it won't be shown again
Making Authenticated Requests
curl -H "Authorization: Bearer sk_live_your_key_here" \
https://saywall.io/api/v1/testimonialsSecurity best practices:
- Never expose your API key in client-side JavaScript
- Store keys in environment variables (
SAYWALL_API_KEY) - Use a server-side proxy for browser-based apps
- Rotate keys periodically
Endpoints
List Testimonials
GET /api/v1/testimonials
Returns a paginated list of approved testimonials for your project.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number |
limit | integer | 20 | Items per page (1-100) |
sort | string | newest | Sort order: newest, oldest, rating |
rating | integer | — | Exact rating filter (1-5) |
rating_min | integer | — | Minimum rating (1-5) |
rating_max | integer | — | Maximum rating (1-5) |
source | string | — | Comma-separated sources: manual, twitter, google, trustpilot, public_form, g2 |
media_type | string | — | Comma-separated types: text, video, image |
tag | string | — | Comma-separated tag names |
tag_mode | string | any | Tag matching: any (has any tag), all (has all tags), exclude (excludes tags) |
search | string | — | Text search in testimonial content (max 200 chars) |
Example Request
curl -H "Authorization: Bearer sk_live_..." \
"https://saywall.io/api/v1/testimonials?tag=saas,b2b&sort=rating&limit=10"Example Response
{
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"content": "SayWall made it incredibly easy to collect and display our customer testimonials.",
"author": {
"name": "Jane Smith",
"role": "Head of Marketing",
"handle": null,
"avatar_url": "https://example.com/avatar.jpg"
},
"rating": 5,
"source": "manual",
"source_url": null,
"media_type": "text",
"media_url": null,
"tags": ["saas", "b2b"],
"posted_at": "2026-03-15T10:30:00Z",
"created_at": "2026-03-15T10:30:00Z",
"video": null
}
],
"pagination": {
"page": 1,
"limit": 10,
"total": 42,
"total_pages": 5,
"has_next": true,
"has_prev": false
}
}Get Single Testimonial
GET /api/v1/testimonials/:id
Returns a single testimonial by ID.
Example Request
curl -H "Authorization: Bearer sk_live_..." \
https://saywall.io/api/v1/testimonials/550e8400-e29b-41d4-a716-446655440000Example Response
{
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"content": "SayWall made it incredibly easy to collect and display our customer testimonials.",
"author": {
"name": "Jane Smith",
"role": "Head of Marketing",
"handle": null,
"avatar_url": "https://example.com/avatar.jpg"
},
"rating": 5,
"source": "manual",
"source_url": null,
"media_type": "text",
"media_url": null,
"tags": ["saas", "b2b"],
"posted_at": "2026-03-15T10:30:00Z",
"created_at": "2026-03-15T10:30:00Z",
"video": null
}
}Filtering Examples
By rating
# Testimonials with exactly 5 stars
?rating=5
# Testimonials with 4 or 5 stars
?rating_min=4
# Testimonials with 1-3 stars
?rating_max=3By tag
# Has any of these tags
?tag=saas,enterprise
# Has ALL of these tags
?tag=saas,enterprise&tag_mode=all
# Exclude testimonials with these tags
?tag=internal&tag_mode=excludeBy source
# Only Google and Trustpilot reviews
?source=google,trustpilot
# Only manually added testimonials
?source=manualBy media type
# Only video testimonials
?media_type=video
# Text and image testimonials
?media_type=text,imageText search
# Search testimonial content
?search=amazing productCombined filters
# 5-star Google reviews tagged "enterprise", newest first
?rating=5&source=google&tag=enterprise&sort=newest&limit=5Pagination
All list responses include a pagination object:
{
"pagination": {
"page": 2,
"limit": 20,
"total": 85,
"total_pages": 5,
"has_next": true,
"has_prev": true
}
}Iterate through pages:
let page = 1;
let hasNext = true;
while (hasNext) {
const res = await fetch(
`https://saywall.io/api/v1/testimonials?page=${page}&limit=100`,
{ headers: { Authorization: `Bearer ${API_KEY}` } }
);
const { data, pagination } = await res.json();
// Process data...
hasNext = pagination.has_next;
page++;
}Rate Limiting
API requests are limited to 60 requests per minute per API key.
Every response includes rate limit headers:
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests per window |
X-RateLimit-Remaining | Requests remaining in current window |
X-RateLimit-Reset | Unix timestamp when the window resets |
When the limit is exceeded, the API returns 429 Too Many Requests with a Retry-After header:
{
"error": {
"code": "rate_limited",
"message": "Rate limit exceeded. Try again in 45 seconds."
}
}Error Codes
| Status | Code | Description |
|---|---|---|
400 | bad_request | Invalid query parameters |
401 | unauthorized | Missing or invalid API key |
403 | forbidden | API access requires Pro plan |
404 | not_found | Testimonial not found |
429 | rate_limited | Too many requests |
500 | internal_error | Server error |
All error responses follow this format:
{
"error": {
"code": "error_code",
"message": "Human-readable description"
}
}Code Examples
curl
# List all testimonials
curl -H "Authorization: Bearer $SAYWALL_API_KEY" \
https://saywall.io/api/v1/testimonials
# Get 5-star testimonials tagged "featured"
curl -H "Authorization: Bearer $SAYWALL_API_KEY" \
"https://saywall.io/api/v1/testimonials?rating=5&tag=featured&limit=5"JavaScript / TypeScript
const API_KEY = process.env.SAYWALL_API_KEY;
async function getTestimonials(params?: Record<string, string>) {
const url = new URL("https://saywall.io/api/v1/testimonials");
if (params) {
Object.entries(params).forEach(([key, value]) =>
url.searchParams.set(key, value)
);
}
const res = await fetch(url.toString(), {
headers: { Authorization: `Bearer ${API_KEY}` },
});
if (!res.ok) {
const { error } = await res.json();
throw new Error(error.message);
}
return res.json();
}
// Usage
const { data, pagination } = await getTestimonials({
tag: "featured",
sort: "rating",
limit: "10",
});Python
import requests
import os
API_KEY = os.environ["SAYWALL_API_KEY"]
BASE_URL = "https://saywall.io/api/v1"
def get_testimonials(**params):
response = requests.get(
f"{BASE_URL}/testimonials",
headers={"Authorization": f"Bearer {API_KEY}"},
params=params,
)
response.raise_for_status()
return response.json()
# Usage
result = get_testimonials(tag="featured", sort="rating", limit=10)
for testimonial in result["data"]:
print(f"{testimonial['author']['name']}: {testimonial['content'][:100]}")React (Server Component)
// app/testimonials/page.tsx
async function getTestimonials() {
const res = await fetch(
"https://saywall.io/api/v1/testimonials?tag=featured&sort=newest",
{
headers: {
Authorization: `Bearer ${process.env.SAYWALL_API_KEY}`,
},
next: { revalidate: 3600 }, // Cache for 1 hour
}
);
return res.json();
}
export default async function TestimonialsPage() {
const { data: testimonials } = await getTestimonials();
return (
<section>
<h2>What our customers say</h2>
<div className="grid grid-cols-3 gap-4">
{testimonials.map((t) => (
<div key={t.id} className="p-4 border rounded-lg">
<p>{t.content}</p>
<footer>
— {t.author.name}
{t.author.role && `, ${t.author.role}`}
</footer>
</div>
))}
</div>
</section>
);
}Video Testimonials
Video testimonials include additional fields in the video object:
{
"media_type": "video",
"media_url": "https://...",
"video": {
"playback_id": "abc123",
"thumbnail_url": "https://image.mux.com/abc123/thumbnail.jpg",
"duration": 45.2,
"width": 1920,
"height": 1080
}
}Use the playback_id with Mux Player for HLS streaming, or use media_url for direct access.
Response Object Reference
Testimonial
| Field | Type | Description |
|---|---|---|
id | string | Unique identifier (UUID) |
content | string | Testimonial text |
author.name | string | null | Author display name |
author.role | string | null | Author job title |
author.handle | string | null | Social media handle |
author.avatar_url | string | null | Profile picture URL |
rating | number | null | Star rating (1-5) |
source | string | Source type |
source_url | string | null | Link to original source |
media_type | string | text, video, or image |
media_url | string | null | Media file URL |
tags | string[] | Tag names |
posted_at | string | ISO 8601 date when originally posted |
created_at | string | ISO 8601 date when added to SayWall |
video | object | null | Video metadata (only for video testimonials) |
Need help?
Have a question or need assistance? Reach us at hello@saywall.io

