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.

GET/api/embed/{slug}/testimonials

Returns 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}/testimonials

Parameters

NameLocationTypeRequiredDescription
slugPathstringYesThe 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

FieldTypeDescription
widgetobjectWidget display settings for the public embed.
widget.show_badgebooleanWhether the Testinora badge should be shown with the widget.
widget.themestring | nullThe saved widget theme, such as light or dark.
testimonialsarrayApproved testimonials ordered from newest to oldest.
testimonials[].idstringUnique testimonial identifier.
testimonials[].author_namestringName provided by the person who submitted the testimonial.
testimonials[].author_rolestring | nullOptional role or title for the testimonial author.
testimonials[].author_companystring | nullOptional company or organization name.
testimonials[].author_avatar_urlstring | nullOptional hosted avatar URL when a customer photo is available.
testimonials[].bodystringThe approved testimonial text.
testimonials[].ratingnumberCustomer rating from 1 to 5.
testimonials[].created_atISO date stringWhen the testimonial was submitted.
POST/api/embed/{slug}/testimonials

Creates 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

FieldTypeRequiredDescription
author_namestringYesCustomer name. Maximum 100 characters.
author_rolestringNoCustomer role or title. Maximum 100 characters.
author_companystringNoCompany or organization name. Maximum 100 characters.
author_emailemail stringNoCustomer email for private owner context. Never returned publicly.
bodystringYesTestimonial text. Maximum 2,000 characters.
ratingintegerYesRating from 1 to 5.
photofileNoOptional 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

StatusReasonExample response
400Invalid JSON, missing required fields, invalid email, or rating outside 1 to 5.{"error":"Name is required"}
403The workspace has reached its testimonial storage limit.{"error":"This workspace is on the Starter plan..."}
404The widget slug does not exist.{"error":"Widget not found"}
429Too many requests from the same IP during the current window.{"error":"Too many requests. Please try again shortly."}
500The 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.

Related developer docs