API Reference
Fetch your testimonials as JSON and build custom integrations.
Testinora exposes public embed endpoints for approved testimonial display and frictionless customer submissions. These endpoints do not require API keys, which makes them useful for static sites, custom widgets, and lightweight integrations.
Public by design
The read endpoint only returns approved testimonials. Customer email addresses are never included in the public JSON response.
/api/embed/{slug}/testimonialsReturns the approved testimonials for a widget slug, along with the widget display settings needed by custom testimonial components.
URL format
https://www.testinora.com/api/embed/{slug}/testimonialsParameters
| Name | Location | Type | Required | Description |
|---|---|---|---|---|
| slug | Path | string | Yes | The widget slug from your Testinora dashboard or collection URL. |
Example curl request
curl -X GET "https://www.testinora.com/api/embed/acme-growth/testimonials" \
-H "Accept: application/json"Example JSON response
{
"widget": {
"show_badge": true,
"theme": "light"
},
"testimonials": [
{
"id": "9d7f4f6e-8c24-4f8d-9f24-8f2c9d6e4a91",
"author_name": "Maya Chen",
"author_role": "Founder",
"author_company": "Northstar Studio",
"author_avatar_url": "https://res.cloudinary.com/testinora/image/upload/avatar.jpg",
"body": "Testinora made it simple to collect proof from our happiest clients and publish it on our pricing page.",
"rating": 5,
"created_at": "2026-04-18T10:32:00.000Z"
}
]
}Response fields
| Field | Type | Description |
|---|---|---|
| widget | object | Widget display settings for the public embed. |
| widget.show_badge | boolean | Whether the Testinora badge should be shown with the widget. |
| widget.theme | string | null | The saved widget theme, such as light or dark. |
| testimonials | array | Approved testimonials ordered from newest to oldest. |
| testimonials[].id | string | Unique testimonial identifier. |
| testimonials[].author_name | string | Name provided by the person who submitted the testimonial. |
| testimonials[].author_role | string | null | Optional role or title for the testimonial author. |
| testimonials[].author_company | string | null | Optional company or organization name. |
| testimonials[].author_avatar_url | string | null | Optional hosted avatar URL when a customer photo is available. |
| testimonials[].body | string | The approved testimonial text. |
| testimonials[].rating | number | Customer rating from 1 to 5. |
| testimonials[].created_at | ISO date string | When the testimonial was submitted. |
/api/embed/{slug}/testimonialsCreates a new testimonial submission for the widget slug. New testimonials are saved as unapproved, so the workspace owner can review them before they appear in public widgets or API responses.
Request body fields
| Field | Type | Required | Description |
|---|---|---|---|
| author_name | string | Yes | Customer name. Maximum 100 characters. |
| author_role | string | No | Customer role or title. Maximum 100 characters. |
| author_company | string | No | Company or organization name. Maximum 100 characters. |
| author_email | email string | No | Customer email for private owner context. Never returned publicly. |
| body | string | Yes | Testimonial text. Maximum 2,000 characters. |
| rating | integer | Yes | Rating from 1 to 5. |
| photo | file | No | Optional JPEG, PNG, or WebP avatar when submitting multipart form data. |
Example request body
{
"author_name": "Maya Chen",
"author_role": "Founder",
"author_company": "Northstar Studio",
"author_email": "maya@example.com",
"body": "Testinora made it simple to collect proof from our happiest clients.",
"rating": 5
}Success response
A successful submission returns HTTP 201 Created.
{
"success": true
}Error responses
| Status | Reason | Example response |
|---|---|---|
| 400 | Invalid JSON, missing required fields, invalid email, or rating outside 1 to 5. | {"error":"Name is required"} |
| 403 | The workspace has reached its testimonial storage limit. | {"error":"This workspace is on the Starter plan..."} |
| 404 | The widget slug does not exist. | {"error":"Widget not found"} |
| 429 | Too many requests from the same IP during the current window. | {"error":"Too many requests. Please try again shortly."} |
| 500 | The testimonial could not be saved. | {"error":"Failed to save"} |
Code examples
JavaScript fetch
async function getTestimonials(slug) {
const response = await fetch(
'https://www.testinora.com/api/embed/' + slug + '/testimonials'
)
if (!response.ok) {
throw new Error('Failed to load testimonials')
}
const data = await response.json()
return data.testimonials
}
const testimonials = await getTestimonials('acme-growth')React hook
'use client'
import { useEffect, useState } from 'react'
export function useTestinoraTestimonials(slug) {
const [testimonials, setTestimonials] = useState([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
let cancelled = false
async function loadTestimonials() {
try {
const response = await fetch(
'https://www.testinora.com/api/embed/' + slug + '/testimonials'
)
if (!response.ok) {
throw new Error('Failed to load testimonials')
}
const data = await response.json()
if (!cancelled) setTestimonials(data.testimonials)
} catch (err) {
if (!cancelled) setError(err)
} finally {
if (!cancelled) setLoading(false)
}
}
loadTestimonials()
return () => {
cancelled = true
}
}, [slug])
return { testimonials, loading, error }
}Python requests
import requests
url = "https://www.testinora.com/api/embed/acme-growth/testimonials"
payload = {
"author_name": "Maya Chen",
"author_role": "Founder",
"author_company": "Northstar Studio",
"author_email": "maya@example.com",
"body": "Testinora made it simple to collect and publish client proof.",
"rating": 5,
}
response = requests.post(url, json=payload, timeout=10)
response.raise_for_status()
print(response.json())Rate limits
60 requests per minute per IP
Public API consumers should stay under 60 requests per minute per IP. Rate limited responses return HTTP 429 with a JSON error body and retry headers when available.
Coming soon
Authenticated API with API keys
A full authenticated API for managing widgets, approvals, and analytics is coming soon. Email hello@testinora.com to join the waitlist.