# Testivora API — LLM reference Testivora turns testimonials into marketing assets (embeddable walls, social carousels, case studies). This REST API gives programmatic access to an account's testimonials, spaces, and walls. Use it to import testimonials collected elsewhere, sync approved testimonials into your own site/dashboard, or manage their status. - Base URL: https://api.testivora.com - Format: JSON over HTTPS. All timestamps are ISO-8601 strings. - Versioned in the path: /v1/. OpenAPI spec: https://api.testivora.com/v1/openapi.json - Availability: Growth and Agency (scale) plans only. ## Authentication Every request needs a Bearer API key, created in the Testivora dashboard at Settings → API. Send it as a header: Authorization: Bearer tv_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Keys are account-scoped: a key only ever sees the data of the account that created it. Keys have scopes: "read" (GET) and/or "write" (POST/PATCH). Auth failures: - 401 unauthorized — missing Authorization header - 401 invalid_api_key — unknown or revoked key - 403 plan_required — the account's plan has no API access - 403 insufficient_scope — a read-only key attempted a write ## Errors Consistent envelope, standard HTTP status codes: { "error": { "type": "not_found", "message": "..." } } - 400 missing_parameter / invalid_json — bad request shape - 402 quota_exceeded — monthly plan quota reached (body adds limit, used, tier) - 403 plan_required / insufficient_scope - 404 not_found — resource not in your account - 422 invalid_text / invalid_status / invalid_rating / invalid_author - 429 rate_limited — too many requests (Retry-After header in seconds) ## Rate limits (per API key) - Reads: 120 req/min (burst 240) - Writes: 30 req/min (burst 60) These are independent of the monthly tier quotas that govern how many testimonials you can create per month. ## Pagination List endpoints are cursor-paginated: { "object": "list", "data": [...], "has_more": true, "next_cursor": "abc" } Pass ?cursor=&limit=50. limit defaults to 25, max 100. ## Media URLs video.url, video.thumbnail_url, author.photo_url (and space logo_url) are SIGNED URLs that expire (~1 hour). Re-fetch the resource for fresh URLs; do not store them. External URLs you set yourself are returned unchanged. ## Endpoints ### GET /v1/account Returns tier, api_access, current usage and plan limits (null = unlimited). curl https://api.testivora.com/v1/account -H "Authorization: Bearer tv_live_..." ### GET /v1/spaces and GET /v1/spaces/{id} List spaces / fetch one. Space object: { id, object:"space", name, slug, description, primary_color, background_style, logo_url, testimonial_count, approved_count, public_wall_url, created_at } ### GET /v1/testimonials List testimonials within a space. Query params: - space_id (REQUIRED) — the space to list from - status (optional) — pending | approved | archived; omit for all - limit (optional, default 25, max 100) - cursor (optional) — next_cursor from a previous page curl "https://api.testivora.com/v1/testimonials?space_id=SPACE_ID&status=approved&limit=50" \ -H "Authorization: Bearer tv_live_..." ### GET /v1/testimonials/{id} Fetch a single testimonial. ### POST /v1/testimonials Create a TEXT testimonial (imports one collected elsewhere). Counts against the monthly testimonial quota. Video testimonials are not creatable via the API in v1. Body: { "space_id": "REQUIRED", "text": "REQUIRED", "rating": 1-5, "status": "approved"|"pending" (default approved), "tags": ["..."], "author": { "name", "title", "company", "photo_url", "email", "social": { "linkedin","x","instagram","website","youtube" } } } curl -X POST https://api.testivora.com/v1/testimonials \ -H "Authorization: Bearer tv_live_..." \ -H "Content-Type: application/json" \ -d '{"space_id":"SPACE_ID","text":"Cerré 3 clientes en una semana.","rating":5,"author":{"name":"Ana López"}}' Returns 201 with the created testimonial. ### PATCH /v1/testimonials/{id} Update status, featured, and/or tags. Body (all optional): { "status": "pending"|"approved"|"archived", "featured": true|false, "tags": ["..."] } curl -X PATCH https://api.testivora.com/v1/testimonials/ID \ -H "Authorization: Bearer tv_live_..." \ -H "Content-Type: application/json" \ -d '{"status":"approved","featured":true}' ### Testimonial object { id, object:"testimonial", space_id, status, type:"video"|"text", text, rating, featured, tags:[], highlight, power_quotes:[], transcription, author:{ name, title, company, photo_url, social }, video: { url, thumbnail_url, camera_url, duration_seconds, content_type } | null, images:[ { url, caption } ], created_at, published_at } ### GET /v1/walls , GET /v1/walls/{id} , GET /v1/walls?space_slug={slug} List wall summaries, or fetch one full wall. A full wall returns its config (template, theme, copy, blocks) plus its approved testimonials ALREADY filtered and ordered exactly as the public wall renders them — drop them into your UI. ## Webhooks (outbound) Register HTTPS endpoints in the dashboard (Settings → Webhooks). We POST a signed JSON event whenever a subscribed event fires. Events: testimonial.created, testimonial.approved, testimonial.deleted. Envelope: { "id": "evt_…", "object": "event", "event": "testimonial.approved", "version": "1.0", "created": "", "attempt": 1, "data": { } } The id is stable across retries — dedupe on it. For testimonial.deleted, data is { id, object:"testimonial", space_id, deleted:true }. Verify each delivery: the header is Testivora-Signature: t=,v1= where hex = HMAC-SHA256(secret, `${t}.${rawBody}`). Recompute with your endpoint's signing secret and compare in constant time. Other headers: Testivora-Event, Testivora-Event-Id, Testivora-Delivery. Delivery: return 2xx fast and process async. Non-2xx/timeout (10s) retries with exponential backoff (up to 6 attempts: 1m, 5m, 30m, 2h, 6h) then dead-letters. Endpoints auto-pause after many consecutive failures. ## Notes for integration - Always handle 402 (quota) and 429 (rate limit, honor Retry-After). - Treat media URLs as ephemeral. - space_id is required to list testimonials; call GET /v1/spaces first to discover space ids.